From 46842200f191f9de0f0b76b8b666a84f0c9b4bb0 Mon Sep 17 00:00:00 2001 From: tompzf <141007871+tompzf@users.noreply.github.com> Date: Fri, 3 Jul 2026 14:53:48 +0200 Subject: [PATCH] connection between 2 systems and bug fixes (#14) --- CMakeLists.txt | 3 + README.md | 17 + doxygen.config | 1089 ++-- .../autoheadlight_console.cpp | 2 +- .../autoheadlight_app/autoheadlight_console.h | 36 +- .../auto_headlamp_example/fmu_files/model.cpp | 5 +- examples/door_demo_example/CMakeLists.txt | 91 +- .../config/front_left_door_example.toml | 4 +- .../config/front_right_door_example.toml | 4 +- .../config/rear_left_door_example.toml | 4 +- .../config/rear_right_door_example.toml | 4 +- .../door_demo_example/door_app/console.cpp | 24 +- .../door_app/door_application.cpp | 19 +- .../door_app/door_extern_application.cpp | 3 +- .../door_app/include/console.h | 65 +- .../door_service/complex_service.h | 16 +- .../door_service/lock_doors_thread.h | 12 +- .../Doors2ExampleFMU/model.cpp | 5 +- .../resources/front_left_door_example.toml | 4 +- .../resources/front_right_door_example.toml | 4 +- .../Doors4ExampleFMU/model.cpp | 5 +- .../resources/front_left_door_example.toml | 4 +- .../resources/front_right_door_example.toml | 4 +- .../resources/rear_left_door_example.toml | 4 +- .../resources/rear_right_door_example.toml | 4 +- ...xample.csv => vss_doors_front_example.csv} | 8 - .../vss_doors_rear_example.csv | 10 + .../open_trunk_app/console.cpp | 54 +- .../open_trunk_app/console.h | 1 + .../example_app/console.cpp | 3 +- .../example_app/control.cpp | 2 +- .../system_demo_example/example_app/control.h | 28 +- examples/system_demo_example/fmu/model.cpp | 5 +- export/interfaces/app.idl | 97 +- export/interfaces/com.idl | 65 +- export/interfaces/config.idl | 11 + export/interfaces/core.idl | 80 +- export/interfaces/core_ps.idl | 2 + export/interfaces/ipc.idl | 7 +- export/interfaces/permission.idl | 96 + export/interfaces/repository.idl | 37 +- export/interfaces/toml.idl | 115 +- export/support/any.inl | 5 + export/support/app_control.h | 146 +- export/support/component_impl.h | 270 +- export/support/interface_ptr.h | 8 +- export/support/local_service_access.h | 686 +- export/support/param_impl.h | 39 +- export/support/param_impl.inl | 11 +- export/support/pssup.inl | 19 + export/support/sdv_core.h | 352 +- export/support/signal_support.h | 8 +- export/support/simple_toml.h | 1010 +++ export/support/toml.h | 273 +- funding_logos/CHIPS_logo_RGB_full_colour.png | Bin 0 -> 161988 bytes funding_logos/EN_Co-fundedbytheEU_RGB_POS.png | Bin 0 -> 69534 bytes funding_logos/HAL4SDV_logo_on_white.jpg | Bin 0 -> 125863 bytes funding_logos/Shift2sdv_logo_tagline_blue.svg | 56 + global/dbcparser/dbcparser.cpp | 4 +- global/debug_log.h | 8 +- global/scheduler/scheduler.h | 50 +- .../{scheduler.cpp => scheduler.inl} | 53 +- global/tracefifo/trace_fifo.h | 2 + global/unique_id.h | 114 + sdv_executables/error_msg.h | 12 +- sdv_executables/sdv_control/CMakeLists.txt | 2 +- .../sdv_control/connection_config.cpp | 471 ++ .../sdv_control/connection_config.h | 98 + sdv_executables/sdv_control/context.h | 1 + sdv_executables/sdv_control/installation.cpp | 2 +- sdv_executables/sdv_control/list_elements.cpp | 150 +- sdv_executables/sdv_control/list_elements.h | 13 +- sdv_executables/sdv_control/main.cpp | 85 +- .../sdv_control/start_stop_service.cpp | 13 +- .../sdv_control/startup_shutdown.cpp | 10 +- sdv_executables/sdv_core/main.cpp | 72 +- sdv_executables/sdv_dbc_util/fmu_templates.h | 5 +- sdv_executables/sdv_dbc_util/main.cpp | 2 +- .../generator/proxy_generator.cpp | 9 + sdv_executables/sdv_idl_compiler/main.cpp | 2 +- sdv_executables/sdv_idl_compiler/source.cpp | 3 +- sdv_executables/sdv_iso/main.cpp | 99 +- sdv_executables/sdv_local_shutdown/main.cpp | 8 +- sdv_executables/sdv_packager/core_control.cpp | 1 + sdv_executables/sdv_packager/environment.cpp | 6 +- sdv_executables/sdv_packager/main.cpp | 10 +- sdv_executables/sdv_packager/packager.cpp | 6 +- sdv_executables/sdv_trace_mon/main.cpp | 10 +- sdv_executables/sdv_vss_util/main.cpp | 2 +- sdv_services/CMakeLists.txt | 21 +- .../can_communication_sim/can_com_sim.h | 2 +- .../can_com_sockets.cpp | 2 +- .../can_com_sockets.h | 2 +- sdv_services/core/CMakeLists.txt | 3 +- sdv_services/core/app_config.cpp | 73 +- sdv_services/core/app_config.h | 6 - sdv_services/core/app_config_file.cpp | 30 +- sdv_services/core/app_config_file.h | 3 + sdv_services/core/app_control.cpp | 190 +- sdv_services/core/app_control.h | 27 +- sdv_services/core/app_settings.cpp | 1269 +++- sdv_services/core/app_settings.h | 277 +- sdv_services/core/installation_composer.cpp | 16 +- sdv_services/core/iso_monitor.cpp | 4 +- sdv_services/core/iso_monitor.h | 4 +- sdv_services/core/local_shutdown_request.h | 2 + sdv_services/core/logger.cpp | 3 +- sdv_services/core/module.cpp | 4 + sdv_services/core/module_control.cpp | 6 +- sdv_services/core/permission_control.cpp | 167 + sdv_services/core/permission_control.h | 263 + sdv_services/core/repository.cpp | 546 +- sdv_services/core/repository.h | 94 +- sdv_services/core/sdv_core.h | 2 + .../core/toml_parser/code_snippet.cpp | 25 +- .../core/toml_parser/miscellaneous.cpp | 19 + sdv_services/core/toml_parser/miscellaneous.h | 10 + .../core/toml_parser/parser_node_indexer.cpp | 145 + .../core/toml_parser/parser_node_indexer.h | 227 + .../core/toml_parser/parser_node_toml.cpp | 1081 ++-- .../core/toml_parser/parser_node_toml.h | 443 +- sdv_services/core/toml_parser/parser_toml.cpp | 84 +- sdv_services/core/toml_parser/parser_toml.h | 82 +- sdv_services/ipc_com/CMakeLists.txt | 2 - sdv_services/ipc_com/com_channel.cpp | 2 +- sdv_services/ipc_com/com_channel.h | 8 +- sdv_services/ipc_com/com_ctrl.cpp | 9 +- sdv_services/ipc_com/com_ctrl.h | 2 +- sdv_services/ipc_connect/client.cpp | 255 +- sdv_services/ipc_connect/client.h | 79 +- sdv_services/ipc_connect/listener.cpp | 100 +- sdv_services/ipc_connect/listener.h | 55 +- sdv_services/ipc_shared_mem/channel_mgnt.cpp | 6 +- sdv_services/ipc_shared_mem/channel_mgnt.h | 4 +- sdv_services/ipc_shared_mem/connection.cpp | 24 +- sdv_services/ipc_shared_mem/connection.h | 4 +- sdv_services/ipc_shared_mem/watchdog.cpp | 2 +- sdv_services/ipc_shared_mem/watchdog.h | 3 +- .../process_control/process_control.cpp | 7 +- .../process_control/process_control.h | 16 +- sdv_services/task_timer/tasktimer.cpp | 12 +- sdv_services/task_timer/tasktimer.h | 2 + sdv_services/uds_unix_sockets/CMakeLists.txt | 45 +- .../uds_unix_sockets/channel_mgnt.cpp | 146 +- sdv_services/uds_unix_sockets/channel_mgnt.h | 16 +- sdv_services/uds_unix_sockets/connection.cpp | 761 ++- sdv_services/uds_unix_sockets/connection.h | 36 +- sdv_services/uds_unix_sockets/watchdog.cpp | 58 + sdv_services/uds_unix_sockets/watchdog.h | 47 + sdv_services/uds_unix_tunnel/CMakeLists.txt | 49 +- sdv_services/uds_unix_tunnel/channel_mgnt.cpp | 420 +- sdv_services/uds_unix_tunnel/channel_mgnt.h | 52 +- sdv_services/uds_unix_tunnel/connection.cpp | 37 +- sdv_services/uds_unix_tunnel/connection.h | 9 + sdv_services/uds_unix_tunnel/watchdog.cpp | 58 + sdv_services/uds_unix_tunnel/watchdog.h | 47 + sdv_services/uds_win_sockets/CMakeLists.txt | 48 +- sdv_services/uds_win_sockets/channel_mgnt.cpp | 478 +- sdv_services/uds_win_sockets/channel_mgnt.h | 74 +- sdv_services/uds_win_sockets/connection.cpp | 266 +- sdv_services/uds_win_sockets/connection.h | 36 +- sdv_services/uds_win_sockets/watchdog.cpp | 58 + sdv_services/uds_win_sockets/watchdog.h | 47 + sdv_services/uds_win_tunnel/CMakeLists.txt | 47 +- sdv_services/uds_win_tunnel/channel_mgnt.cpp | 844 ++- sdv_services/uds_win_tunnel/channel_mgnt.h | 31 +- sdv_services/uds_win_tunnel/connection.cpp | 106 +- sdv_services/uds_win_tunnel/connection.h | 10 +- sdv_services/uds_win_tunnel/watchdog.cpp | 58 + sdv_services/uds_win_tunnel/watchdog.h | 47 + tests/CMakeLists.txt | 7 +- tests/cmake/run_gtest_serialized.cmake | 40 + .../app_connect/CMakeLists.txt | 156 + .../app_connect_tests_shared_mem.cpp | 519 ++ .../app_connect/app_connect_tests_uds.cpp | 519 ++ .../app_connect_tests_uds_tunnel.cpp | 557 ++ tests/component_tests/app_connect/main.cpp | 31 + .../app_connect/main_server.cpp | 99 + .../app_connect/test_component.idl | 46 + .../test_component_basic_service.cpp | 72 + .../test_component_complex_service.cpp | 81 + .../app_connect/test_component_device.cpp | 76 + .../app_connect/test_component_ps.cpp} | 8 +- .../test_component_system_service.cpp | 69 + .../app_connect/test_component_utility.cpp | 70 + .../app_control/CMakeLists.txt | 10 +- .../app_control_test_event_handler.cpp | 76 +- .../app_control_test_mgnt_class.cpp | 77 +- .../app_control_test_no_event_handler.cpp | 78 +- .../config_install/module_install_test.cpp | 71 +- .../data_dispatch_service_test.cpp | 16 +- .../transaction_test.cpp | 16 +- .../dbc_util/dbc_util_test.cpp | 48 +- .../param_utils/CMakeLists.txt | 36 + .../param_utils/get_parameter.cpp | 213 + .../param_utils/main.cpp} | 16 +- .../param_utils/resolve_text.cpp | 168 + .../component_tests/repository/CMakeLists.txt | 2 +- .../repository/isolation_test.cpp | 8 +- .../repository/repository_test.cpp | 98 +- .../repository/test_component.cpp | 2 +- tests/include/test_watchdog.h | 6 +- .../include/can_com_test_helper.h | 4 +- .../src/can_com_manual_test_silkit.cpp | 28 +- .../src/can_com_test_silkit.cpp | 8 +- .../silkit_can_com_tests/src/can_reader.cpp | 5 +- .../silkit_can_com_tests/src/can_writer.cpp | 5 +- tests/sdv_core_reloc.toml | 18 +- .../CMakeLists.txt | 16 +- tests/unit_tests/app_settings/main.cpp | 41 + .../app_settings/settings_config.cpp | 537 ++ .../app_settings/startup_config.cpp | 341 + .../core_loader/core_loader_test.cpp | 28 +- .../unit_tests/dbc_parser/dbc_parser_test.cpp | 196 +- .../composer_test_suite.cpp | 1 + tests/unit_tests/ipc_com/CMakeLists.txt | 6 +- tests/unit_tests/ipc_com/include.h | 1300 ++++ .../{ipc_com.cpp => ipc_com_sharedmem.cpp} | 1668 ++--- tests/unit_tests/ipc_com/ipc_com_uds.cpp | 5476 ++++++++++++++++ .../unit_tests/ipc_com/ipc_com_uds_tunnel.cpp | 5534 +++++++++++++++++ tests/unit_tests/ipc_com/main.cpp | 3 +- tests/unit_tests/ipc_connect/CMakeLists.txt | 6 +- tests/unit_tests/ipc_connect/ipc_connect.cpp | 307 - .../ipc_connect/ipc_connect_false_positiv.cpp | 86 + .../ipc_connect/ipc_connect_shared_mem.cpp | 181 + .../ipc_connect/ipc_connect_uds.cpp | 233 + .../ipc_connect/ipc_connect_uds_tunnel.cpp | 278 + tests/unit_tests/module_control/main.cpp | 1 + tests/unit_tests/module_control/mock.h | 4 +- .../named_mutex/named_mutex_test.cpp | 28 +- tests/unit_tests/parameters/param_access.cpp | 25 +- .../permission_control/CMakeLists.txt | 33 + .../main.cpp | 3 +- .../permission_control_tests.cpp | 264 + .../process_control/process_control_app.cpp | 8 +- .../process_control/process_control_tests.cpp | 110 +- tests/unit_tests/repository/main.cpp | 2 + tests/unit_tests/repository/mock.h | 30 +- .../unit_tests/repository/repository_test.cpp | 22 +- tests/unit_tests/scheduler/scheduler_test.cpp | 19 +- tests/unit_tests/sdv_control/includes.h | 4 +- tests/unit_tests/shared_mem/app_connect.cpp | 14 +- .../in_process_mem_buffer_tests.cpp | 184 +- tests/unit_tests/shared_mem/pattern_gen.cpp | 6 +- tests/unit_tests/shared_mem/pattern_gen.h | 51 +- .../shared_mem/shared_mem_buffer_tests.cpp | 16 +- .../shared_mem/shared_mem_connect.cpp | 94 +- .../shared_mem_large_data_tests.cpp | 58 +- .../simple_toml_parser/CMakeLists.txt | 46 + tests/unit_tests/simple_toml_parser/main.cpp | 28 + .../simple_toml_parser/parser_tests.cpp | 868 +++ .../src/can_com_test_socket.cpp | 66 +- tests/unit_tests/toml_parser/CMakeLists.txt | 17 +- .../toml_parser/cascade_insert_node.cpp | 536 ++ .../toml_parser/character_reader_tests.cpp | 1 + ..._combine_reduce.cpp => combine_reduce.cpp} | 173 +- .../toml_parser/comparison_tests.cpp | 386 ++ ...e_toml_delete_node.cpp => delete_node.cpp} | 250 +- .../toml_parser/generate_toml_insert_node.cpp | 1068 ---- .../generate_toml_switch_inline.cpp | 50 - .../toml_parser/generate_toml_tests.cpp | 209 +- .../generate_toml_with_transfer.cpp | 383 +- ..._getset_comment.cpp => getset_comment.cpp} | 100 +- .../unit_tests/toml_parser/indexer_tests.cpp | 119 + tests/unit_tests/toml_parser/insert_node.cpp | 990 +++ tests/unit_tests/toml_parser/lexer_tests.cpp | 69 +- tests/unit_tests/toml_parser/main.cpp | 4 + .../toml_parser/make_inline_standard.cpp | 289 + ...cellaneous_tests.cpp => miscellaneous.cpp} | 91 +- tests/unit_tests/toml_parser/parser_tests.cpp | 79 +- .../statement_boundary_detection.cpp | 159 +- tests/unit_tests/trace_fifo/fifo_test.cpp | 2 +- tests/unit_tests/unique_id/CMakeLists.txt | 41 + tests/unit_tests/unique_id/unique_id_test.cpp | 85 + tests/unit_tests/unix_sockets/CMakeLists.txt | 6 +- .../unix_sockets/uds_connect_tests.cpp | 202 +- tests/unit_tests/unix_tunnel/CMakeLists.txt | 12 +- .../unix_tunnel_channel_mgnt_tests.cpp | 240 +- tests/unit_tests/win_sockets/CMakeLists.txt | 2 - .../win_sockets/win_connect_tests.cpp | 141 +- tests/unit_tests/win_tunnel/CMakeLists.txt | 6 - .../win_tunnel_channel_mgnt_tests.cpp | 151 +- .../win_tunnel/win_tunnel_connect_tests.cpp | 46 +- .../win_tunnel_negative_edge_tests.cpp | 63 +- 284 files changed, 35200 insertions(+), 8265 deletions(-) rename examples/door_demo_example/{vss_doors_example.csv => vss_doors_front_example.csv} (53%) create mode 100644 examples/door_demo_example/vss_doors_rear_example.csv create mode 100644 export/interfaces/permission.idl create mode 100644 export/support/simple_toml.h create mode 100644 funding_logos/CHIPS_logo_RGB_full_colour.png create mode 100644 funding_logos/EN_Co-fundedbytheEU_RGB_POS.png create mode 100644 funding_logos/HAL4SDV_logo_on_white.jpg create mode 100644 funding_logos/Shift2sdv_logo_tagline_blue.svg rename global/scheduler/{scheduler.cpp => scheduler.inl} (74%) create mode 100644 global/unique_id.h create mode 100644 sdv_executables/sdv_control/connection_config.cpp create mode 100644 sdv_executables/sdv_control/connection_config.h create mode 100644 sdv_services/core/permission_control.cpp create mode 100644 sdv_services/core/permission_control.h create mode 100644 sdv_services/core/toml_parser/parser_node_indexer.cpp create mode 100644 sdv_services/core/toml_parser/parser_node_indexer.h create mode 100644 sdv_services/uds_unix_sockets/watchdog.cpp create mode 100644 sdv_services/uds_unix_sockets/watchdog.h create mode 100644 sdv_services/uds_unix_tunnel/watchdog.cpp create mode 100644 sdv_services/uds_unix_tunnel/watchdog.h create mode 100644 sdv_services/uds_win_sockets/watchdog.cpp create mode 100644 sdv_services/uds_win_sockets/watchdog.h create mode 100644 sdv_services/uds_win_tunnel/watchdog.cpp create mode 100644 sdv_services/uds_win_tunnel/watchdog.h create mode 100644 tests/cmake/run_gtest_serialized.cmake create mode 100644 tests/component_tests/app_connect/CMakeLists.txt create mode 100644 tests/component_tests/app_connect/app_connect_tests_shared_mem.cpp create mode 100644 tests/component_tests/app_connect/app_connect_tests_uds.cpp create mode 100644 tests/component_tests/app_connect/app_connect_tests_uds_tunnel.cpp create mode 100644 tests/component_tests/app_connect/main.cpp create mode 100644 tests/component_tests/app_connect/main_server.cpp create mode 100644 tests/component_tests/app_connect/test_component.idl create mode 100644 tests/component_tests/app_connect/test_component_basic_service.cpp create mode 100644 tests/component_tests/app_connect/test_component_complex_service.cpp create mode 100644 tests/component_tests/app_connect/test_component_device.cpp rename tests/{unit_tests/app_connect/app_connect_local.cpp => component_tests/app_connect/test_component_ps.cpp} (69%) create mode 100644 tests/component_tests/app_connect/test_component_system_service.cpp create mode 100644 tests/component_tests/app_connect/test_component_utility.cpp create mode 100644 tests/component_tests/param_utils/CMakeLists.txt create mode 100644 tests/component_tests/param_utils/get_parameter.cpp rename tests/{unit_tests/toml_parser/generate_toml_miscellaneous.cpp => component_tests/param_utils/main.cpp} (64%) create mode 100644 tests/component_tests/param_utils/resolve_text.cpp rename tests/unit_tests/{app_connect => app_settings}/CMakeLists.txt (57%) create mode 100644 tests/unit_tests/app_settings/main.cpp create mode 100644 tests/unit_tests/app_settings/settings_config.cpp create mode 100644 tests/unit_tests/app_settings/startup_config.cpp create mode 100644 tests/unit_tests/ipc_com/include.h rename tests/unit_tests/ipc_com/{ipc_com.cpp => ipc_com_sharedmem.cpp} (73%) create mode 100644 tests/unit_tests/ipc_com/ipc_com_uds.cpp create mode 100644 tests/unit_tests/ipc_com/ipc_com_uds_tunnel.cpp delete mode 100644 tests/unit_tests/ipc_connect/ipc_connect.cpp create mode 100644 tests/unit_tests/ipc_connect/ipc_connect_false_positiv.cpp create mode 100644 tests/unit_tests/ipc_connect/ipc_connect_shared_mem.cpp create mode 100644 tests/unit_tests/ipc_connect/ipc_connect_uds.cpp create mode 100644 tests/unit_tests/ipc_connect/ipc_connect_uds_tunnel.cpp create mode 100644 tests/unit_tests/permission_control/CMakeLists.txt rename tests/unit_tests/{app_connect => permission_control}/main.cpp (93%) create mode 100644 tests/unit_tests/permission_control/permission_control_tests.cpp create mode 100644 tests/unit_tests/simple_toml_parser/CMakeLists.txt create mode 100644 tests/unit_tests/simple_toml_parser/main.cpp create mode 100644 tests/unit_tests/simple_toml_parser/parser_tests.cpp create mode 100644 tests/unit_tests/toml_parser/cascade_insert_node.cpp rename tests/unit_tests/toml_parser/{generate_toml_combine_reduce.cpp => combine_reduce.cpp} (74%) create mode 100644 tests/unit_tests/toml_parser/comparison_tests.cpp rename tests/unit_tests/toml_parser/{generate_toml_delete_node.cpp => delete_node.cpp} (76%) delete mode 100644 tests/unit_tests/toml_parser/generate_toml_insert_node.cpp delete mode 100644 tests/unit_tests/toml_parser/generate_toml_switch_inline.cpp rename tests/unit_tests/toml_parser/{generate_toml_getset_comment.cpp => getset_comment.cpp} (83%) create mode 100644 tests/unit_tests/toml_parser/indexer_tests.cpp create mode 100644 tests/unit_tests/toml_parser/insert_node.cpp create mode 100644 tests/unit_tests/toml_parser/make_inline_standard.cpp rename tests/unit_tests/toml_parser/{miscellaneous_tests.cpp => miscellaneous.cpp} (96%) create mode 100644 tests/unit_tests/unique_id/CMakeLists.txt create mode 100644 tests/unit_tests/unique_id/unique_id_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7bfc06f..304cd07 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -119,6 +119,9 @@ else() if (WIN32) message("Use g++ compiler under Windows (mingw)...") add_compile_definitions(UNICODE _UNICODE main=wmain) + # MinGW COFF object files can hit section limits for large translation units. + # Enable big object format to avoid assembler 'too many sections' failures. + add_compile_options(-Wa,-mbig-obj) add_link_options(-municode) else() message("Use g++ compiler...") diff --git a/README.md b/README.md index 52737c9..8b86c76 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,20 @@ +The 'Eclipse Open Vehicle API' project is supported by Chips JU. + +arch-module-if + +arch-module-if + +The HAL4SDV project (Grant Agreement No. 101139789) is supported by the Chips Joint Undertaking and its members, including top-up funding by the national authorities. + +arch-module-if + +The Shift2SDV project (Grant Agreement No. 101194245) is supported by the Chips Joint Undertaking and its members, including top-up funding by the national authorities. + +arch-module-if + +--- + + # This is the latest news about 'Eclipse Open Vehicle API' # # for more information see documentation: https://eclipse.dev/openvehicle-api/ diff --git a/doxygen.config b/doxygen.config index d7d3f12..6376615 100644 --- a/doxygen.config +++ b/doxygen.config @@ -1786,515 +1786,515 @@ MATHJAX_CODEFILE = # the search. The filter options can be selected when the cursor is inside the # search box by pressing +. Also here use the # to select a filter and or - to activate or cancel the filter - # option. - # The default value is: YES. - # This tag requires that the tag GENERATE_HTML is set to YES. - - SEARCHENGINE = YES - - # When the SERVER_BASED_SEARCH tag is enabled the search engine will be - # implemented using a web server instead of a web client using JavaScript. There - # are two flavors of web server based searching depending on the EXTERNAL_SEARCH - # setting. When disabled, doxygen will generate a PHP script for searching and - # an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing - # and searching needs to be provided by external tools. See the section - # "External Indexing and Searching" for details. - # The default value is: NO. - # This tag requires that the tag SEARCHENGINE is set to YES. - - SERVER_BASED_SEARCH = NO - - # When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP - # script for searching. Instead the search results are written to an XML file - # which needs to be processed by an external indexer. Doxygen will invoke an - # external search engine pointed to by the SEARCHENGINE_URL option to obtain the - # search results. - # - # Doxygen ships with an example indexer (doxyindexer) and search engine - # (doxysearch.cgi) which are based on the open source search engine library - # Xapian (see: - # https://xapian.org/). - # - # See the section "External Indexing and Searching" for details. - # The default value is: NO. - # This tag requires that the tag SEARCHENGINE is set to YES. - - EXTERNAL_SEARCH = NO - - # The SEARCHENGINE_URL should point to a search engine hosted by a web server - # which will return the search results when EXTERNAL_SEARCH is enabled. - # - # Doxygen ships with an example indexer (doxyindexer) and search engine - # (doxysearch.cgi) which are based on the open source search engine library - # Xapian (see: - # https://xapian.org/). See the section "External Indexing and Searching" for - # details. - # This tag requires that the tag SEARCHENGINE is set to YES. - - SEARCHENGINE_URL = - - # When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed - # search data is written to a file for indexing by an external tool. With the - # SEARCHDATA_FILE tag the name of this file can be specified. - # The default file is: searchdata.xml. - # This tag requires that the tag SEARCHENGINE is set to YES. - - SEARCHDATA_FILE = searchdata.xml - - # When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the - # EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is - # useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple - # projects and redirect the results back to the right project. - # This tag requires that the tag SEARCHENGINE is set to YES. - - EXTERNAL_SEARCH_ID = - - # The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen - # projects other than the one defined by this configuration file, but that are - # all added to the same external search index. Each project needs to have a - # unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of - # to a relative location where the documentation can be found. The format is: - # EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... - # This tag requires that the tag SEARCHENGINE is set to YES. - - EXTRA_SEARCH_MAPPINGS = - - #--------------------------------------------------------------------------- - # Configuration options related to the LaTeX output - #--------------------------------------------------------------------------- - - # If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. - # The default value is: YES. - - GENERATE_LATEX = NO - - # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a - # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of - # it. - # The default directory is: latex. - # This tag requires that the tag GENERATE_LATEX is set to YES. - - LATEX_OUTPUT = latex - - # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be - # invoked. - # - # Note that when not enabling USE_PDFLATEX the default is latex when enabling - # USE_PDFLATEX the default is pdflatex and when in the later case latex is - # chosen this is overwritten by pdflatex. For specific output languages the - # default can have been set differently, this depends on the implementation of - # the output language. - # This tag requires that the tag GENERATE_LATEX is set to YES. - - LATEX_CMD_NAME = latex - - # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate - # index for LaTeX. - # Note: This tag is used in the Makefile / make.bat. - # See also: LATEX_MAKEINDEX_CMD for the part in the generated output file - # (.tex). - # The default file is: makeindex. - # This tag requires that the tag GENERATE_LATEX is set to YES. - - MAKEINDEX_CMD_NAME = makeindex - - # The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to - # generate index for LaTeX. In case there is no backslash (\) as first character - # it will be automatically added in the LaTeX code. - # Note: This tag is used in the generated output file (.tex). - # See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat. - # The default value is: makeindex. - # This tag requires that the tag GENERATE_LATEX is set to YES. - - LATEX_MAKEINDEX_CMD = makeindex - - # If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX - # documents. This may be useful for small projects and may help to save some - # trees in general. - # The default value is: NO. - # This tag requires that the tag GENERATE_LATEX is set to YES. - - COMPACT_LATEX = NO - - # The PAPER_TYPE tag can be used to set the paper type that is used by the - # printer. - # Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x - # 14 inches) and executive (7.25 x 10.5 inches). - # The default value is: a4. - # This tag requires that the tag GENERATE_LATEX is set to YES. - - PAPER_TYPE = a4 - - # The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names - # that should be included in the LaTeX output. The package can be specified just - # by its name or with the correct syntax as to be used with the LaTeX - # \usepackage command. To get the times font for instance you can specify : - # EXTRA_PACKAGES=times or EXTRA_PACKAGES={times} - # To use the option intlimits with the amsmath package you can specify: - # EXTRA_PACKAGES=[intlimits]{amsmath} - # If left blank no extra packages will be included. - # This tag requires that the tag GENERATE_LATEX is set to YES. - - EXTRA_PACKAGES = - - # The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for - # the generated LaTeX document. The header should contain everything until the - # first chapter. If it is left blank doxygen will generate a standard header. It - # is highly recommended to start with a default header using - # doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty - # and then modify the file new_header.tex. See also section "Doxygen usage" for - # information on how to generate the default header that doxygen normally uses. - # - # Note: Only use a user-defined header if you know what you are doing! - # Note: The header is subject to change so you typically have to regenerate the - # default header when upgrading to a newer version of doxygen. The following - # commands have a special meaning inside the header (and footer): For a - # description of the possible markers and block names see the documentation. - # This tag requires that the tag GENERATE_LATEX is set to YES. - - LATEX_HEADER = - - # The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for - # the generated LaTeX document. The footer should contain everything after the - # last chapter. If it is left blank doxygen will generate a standard footer. See - # LATEX_HEADER for more information on how to generate a default footer and what - # special commands can be used inside the footer. See also section "Doxygen - # usage" for information on how to generate the default footer that doxygen - # normally uses. Note: Only use a user-defined footer if you know what you are - # doing! - # This tag requires that the tag GENERATE_LATEX is set to YES. - - LATEX_FOOTER = - - # The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined - # LaTeX style sheets that are included after the standard style sheets created - # by doxygen. Using this option one can overrule certain style aspects. Doxygen - # will copy the style sheet files to the output directory. - # Note: The order of the extra style sheet files is of importance (e.g. the last - # style sheet in the list overrules the setting of the previous ones in the - # list). - # This tag requires that the tag GENERATE_LATEX is set to YES. - - LATEX_EXTRA_STYLESHEET = - - # The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or - # other source files which should be copied to the LATEX_OUTPUT output - # directory. Note that the files will be copied as-is; there are no commands or - # markers available. - # This tag requires that the tag GENERATE_LATEX is set to YES. - - LATEX_EXTRA_FILES = - - # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is - # prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will - # contain links (just like the HTML output) instead of page references. This - # makes the output suitable for online browsing using a PDF viewer. - # The default value is: YES. - # This tag requires that the tag GENERATE_LATEX is set to YES. - - PDF_HYPERLINKS = YES - - # If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as - # specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX - # files. Set this option to YES, to get a higher quality PDF documentation. - # - # See also section LATEX_CMD_NAME for selecting the engine. - # The default value is: YES. - # This tag requires that the tag GENERATE_LATEX is set to YES. - - USE_PDFLATEX = YES - - # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode - # command to the generated LaTeX files. This will instruct LaTeX to keep running - # if errors occur, instead of asking the user for help. - # The default value is: NO. - # This tag requires that the tag GENERATE_LATEX is set to YES. - - LATEX_BATCHMODE = NO - - # If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the - # index chapters (such as File Index, Compound Index, etc.) in the output. - # The default value is: NO. - # This tag requires that the tag GENERATE_LATEX is set to YES. - - LATEX_HIDE_INDICES = NO - - # The LATEX_BIB_STYLE tag can be used to specify the style to use for the - # bibliography, e.g. plainnat, or ieeetr. See - # https://en.wikipedia.org/wiki/BibTeX and \cite for more info. - # The default value is: plain. - # This tag requires that the tag GENERATE_LATEX is set to YES. - - LATEX_BIB_STYLE = plain - - # The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) - # path from which the emoji images will be read. If a relative path is entered, - # it will be relative to the LATEX_OUTPUT directory. If left blank the - # LATEX_OUTPUT directory will be used. - # This tag requires that the tag GENERATE_LATEX is set to YES. - - LATEX_EMOJI_DIRECTORY = - - #--------------------------------------------------------------------------- - # Configuration options related to the RTF output - #--------------------------------------------------------------------------- - - # If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The - # RTF output is optimized for Word 97 and may not look too pretty with other RTF - # readers/editors. - # The default value is: NO. - - GENERATE_RTF = NO - - # The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a - # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of - # it. - # The default directory is: rtf. - # This tag requires that the tag GENERATE_RTF is set to YES. - - RTF_OUTPUT = rtf - - # If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF - # documents. This may be useful for small projects and may help to save some - # trees in general. - # The default value is: NO. - # This tag requires that the tag GENERATE_RTF is set to YES. - - COMPACT_RTF = NO - - # If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will - # contain hyperlink fields. The RTF file will contain links (just like the HTML - # output) instead of page references. This makes the output suitable for online - # browsing using Word or some other Word compatible readers that support those - # fields. - # - # Note: WordPad (write) and others do not support links. - # The default value is: NO. - # This tag requires that the tag GENERATE_RTF is set to YES. - - RTF_HYPERLINKS = NO +# to activate or cancel the filter +# option. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a web server instead of a web client using JavaScript. There +# are two flavors of web server based searching depending on the EXTERNAL_SEARCH +# setting. When disabled, doxygen will generate a PHP script for searching and +# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing +# and searching needs to be provided by external tools. See the section +# "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SERVER_BASED_SEARCH = NO + +# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP +# script for searching. Instead the search results are written to an XML file +# which needs to be processed by an external indexer. Doxygen will invoke an +# external search engine pointed to by the SEARCHENGINE_URL option to obtain the +# search results. +# +# Doxygen ships with an example indexer (doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: +# https://xapian.org/). +# +# See the section "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH = NO + +# The SEARCHENGINE_URL should point to a search engine hosted by a web server +# which will return the search results when EXTERNAL_SEARCH is enabled. +# +# Doxygen ships with an example indexer (doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: +# https://xapian.org/). See the section "External Indexing and Searching" for +# details. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHENGINE_URL = + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed +# search data is written to a file for indexing by an external tool. With the +# SEARCHDATA_FILE tag the name of this file can be specified. +# The default file is: searchdata.xml. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHDATA_FILE = searchdata.xml + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the +# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is +# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple +# projects and redirect the results back to the right project. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH_ID = + +# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen +# projects other than the one defined by this configuration file, but that are +# all added to the same external search index. Each project needs to have a +# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of +# to a relative location where the documentation can be found. The format is: +# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTRA_SEARCH_MAPPINGS = + +#--------------------------------------------------------------------------- +# Configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. +# The default value is: YES. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: latex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. +# +# Note that when not enabling USE_PDFLATEX the default is latex when enabling +# USE_PDFLATEX the default is pdflatex and when in the later case latex is +# chosen this is overwritten by pdflatex. For specific output languages the +# default can have been set differently, this depends on the implementation of +# the output language. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate +# index for LaTeX. +# Note: This tag is used in the Makefile / make.bat. +# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file +# (.tex). +# The default file is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +MAKEINDEX_CMD_NAME = makeindex + +# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to +# generate index for LaTeX. In case there is no backslash (\) as first character +# it will be automatically added in the LaTeX code. +# Note: This tag is used in the generated output file (.tex). +# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat. +# The default value is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_MAKEINDEX_CMD = makeindex + +# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used by the +# printer. +# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x +# 14 inches) and executive (7.25 x 10.5 inches). +# The default value is: a4. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +PAPER_TYPE = a4 + +# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names +# that should be included in the LaTeX output. The package can be specified just +# by its name or with the correct syntax as to be used with the LaTeX +# \usepackage command. To get the times font for instance you can specify : +# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times} +# To use the option intlimits with the amsmath package you can specify: +# EXTRA_PACKAGES=[intlimits]{amsmath} +# If left blank no extra packages will be included. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for +# the generated LaTeX document. The header should contain everything until the +# first chapter. If it is left blank doxygen will generate a standard header. It +# is highly recommended to start with a default header using +# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty +# and then modify the file new_header.tex. See also section "Doxygen usage" for +# information on how to generate the default header that doxygen normally uses. +# +# Note: Only use a user-defined header if you know what you are doing! +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. The following +# commands have a special meaning inside the header (and footer): For a +# description of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for +# the generated LaTeX document. The footer should contain everything after the +# last chapter. If it is left blank doxygen will generate a standard footer. See +# LATEX_HEADER for more information on how to generate a default footer and what +# special commands can be used inside the footer. See also section "Doxygen +# usage" for information on how to generate the default footer that doxygen +# normally uses. Note: Only use a user-defined footer if you know what you are +# doing! +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_FOOTER = + +# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# LaTeX style sheets that are included after the standard style sheets created +# by doxygen. Using this option one can overrule certain style aspects. Doxygen +# will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_STYLESHEET = + +# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the LATEX_OUTPUT output +# directory. Note that the files will be copied as-is; there are no commands or +# markers available. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_FILES = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is +# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will +# contain links (just like the HTML output) instead of page references. This +# makes the output suitable for online browsing using a PDF viewer. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as +# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX +# files. Set this option to YES, to get a higher quality PDF documentation. +# +# See also section LATEX_CMD_NAME for selecting the engine. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode +# command to the generated LaTeX files. This will instruct LaTeX to keep running +# if errors occur, instead of asking the user for help. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BATCHMODE = NO + +# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the +# index chapters (such as File Index, Compound Index, etc.) in the output. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_HIDE_INDICES = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. See +# https://en.wikipedia.org/wiki/BibTeX and \cite for more info. +# The default value is: plain. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BIB_STYLE = plain + +# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) +# path from which the emoji images will be read. If a relative path is entered, +# it will be relative to the LATEX_OUTPUT directory. If left blank the +# LATEX_OUTPUT directory will be used. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EMOJI_DIRECTORY = + +#--------------------------------------------------------------------------- +# Configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The +# RTF output is optimized for Word 97 and may not look too pretty with other RTF +# readers/editors. +# The default value is: NO. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: rtf. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will +# contain hyperlink fields. The RTF file will contain links (just like the HTML +# output) instead of page references. This makes the output suitable for online +# browsing using Word or some other Word compatible readers that support those +# fields. +# +# Note: WordPad (write) and others do not support links. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_HYPERLINKS = NO - # Load stylesheet definitions from file. Syntax is similar to doxygen's - # configuration file, i.e. a series of assignments. You only have to provide - # replacements, missing definitions are set to their default value. - # - # See also section "Doxygen usage" for information on how to generate the - # default style sheet that doxygen normally uses. - # This tag requires that the tag GENERATE_RTF is set to YES. +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# configuration file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. +# +# See also section "Doxygen usage" for information on how to generate the +# default style sheet that doxygen normally uses. +# This tag requires that the tag GENERATE_RTF is set to YES. - RTF_STYLESHEET_FILE = - - # Set optional variables used in the generation of an RTF document. Syntax is - # similar to doxygen's configuration file. A template extensions file can be - # generated using doxygen -e rtf extensionFile. - # This tag requires that the tag GENERATE_RTF is set to YES. +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an RTF document. Syntax is +# similar to doxygen's configuration file. A template extensions file can be +# generated using doxygen -e rtf extensionFile. +# This tag requires that the tag GENERATE_RTF is set to YES. - RTF_EXTENSIONS_FILE = +RTF_EXTENSIONS_FILE = - #--------------------------------------------------------------------------- - # Configuration options related to the man page output - #--------------------------------------------------------------------------- - - # If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for - # classes and files. - # The default value is: NO. +#--------------------------------------------------------------------------- +# Configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for +# classes and files. +# The default value is: NO. - GENERATE_MAN = NO +GENERATE_MAN = NO - # The MAN_OUTPUT tag is used to specify where the man pages will be put. If a - # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of - # it. A directory man3 will be created inside the directory specified by - # MAN_OUTPUT. - # The default directory is: man. - # This tag requires that the tag GENERATE_MAN is set to YES. +# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. A directory man3 will be created inside the directory specified by +# MAN_OUTPUT. +# The default directory is: man. +# This tag requires that the tag GENERATE_MAN is set to YES. - MAN_OUTPUT = man +MAN_OUTPUT = man - # The MAN_EXTENSION tag determines the extension that is added to the generated - # man pages. In case the manual section does not start with a number, the number - # 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is - # optional. - # The default value is: .3. - # This tag requires that the tag GENERATE_MAN is set to YES. +# The MAN_EXTENSION tag determines the extension that is added to the generated +# man pages. In case the manual section does not start with a number, the number +# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is +# optional. +# The default value is: .3. +# This tag requires that the tag GENERATE_MAN is set to YES. - MAN_EXTENSION = .3 +MAN_EXTENSION = .3 - # The MAN_SUBDIR tag determines the name of the directory created within - # MAN_OUTPUT in which the man pages are placed. If defaults to man followed by - # MAN_EXTENSION with the initial . removed. - # This tag requires that the tag GENERATE_MAN is set to YES. +# The MAN_SUBDIR tag determines the name of the directory created within +# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by +# MAN_EXTENSION with the initial . removed. +# This tag requires that the tag GENERATE_MAN is set to YES. - MAN_SUBDIR = - - # If the MAN_LINKS tag is set to YES and doxygen generates man output, then it - # will generate one additional man file for each entity documented in the real - # man page(s). These additional files only source the real man page, but without - # them the man command would be unable to find the correct page. - # The default value is: NO. - # This tag requires that the tag GENERATE_MAN is set to YES. +MAN_SUBDIR = + +# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it +# will generate one additional man file for each entity documented in the real +# man page(s). These additional files only source the real man page, but without +# them the man command would be unable to find the correct page. +# The default value is: NO. +# This tag requires that the tag GENERATE_MAN is set to YES. - MAN_LINKS = NO +MAN_LINKS = NO - #--------------------------------------------------------------------------- - # Configuration options related to the XML output - #--------------------------------------------------------------------------- +#--------------------------------------------------------------------------- +# Configuration options related to the XML output +#--------------------------------------------------------------------------- - # If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that - # captures the structure of the code including all documentation. - # The default value is: NO. +# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that +# captures the structure of the code including all documentation. +# The default value is: NO. - GENERATE_XML = YES +GENERATE_XML = YES - # The XML_OUTPUT tag is used to specify where the XML pages will be put. If a - # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of - # it. - # The default directory is: xml. - # This tag requires that the tag GENERATE_XML is set to YES. +# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: xml. +# This tag requires that the tag GENERATE_XML is set to YES. - XML_OUTPUT = xml - - # If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program - # listings (including syntax highlighting and cross-referencing information) to - # the XML output. Note that enabling this will significantly increase the size - # of the XML output. - # The default value is: YES. - # This tag requires that the tag GENERATE_XML is set to YES. +XML_OUTPUT = xml + +# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program +# listings (including syntax highlighting and cross-referencing information) to +# the XML output. Note that enabling this will significantly increase the size +# of the XML output. +# The default value is: YES. +# This tag requires that the tag GENERATE_XML is set to YES. - XML_PROGRAMLISTING = YES +XML_PROGRAMLISTING = YES - # If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include - # namespace members in file scope as well, matching the HTML output. - # The default value is: NO. - # This tag requires that the tag GENERATE_XML is set to YES. +# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include +# namespace members in file scope as well, matching the HTML output. +# The default value is: NO. +# This tag requires that the tag GENERATE_XML is set to YES. - XML_NS_MEMB_FILE_SCOPE = NO +XML_NS_MEMB_FILE_SCOPE = NO - #--------------------------------------------------------------------------- - # Configuration options related to the DOCBOOK output - #--------------------------------------------------------------------------- +#--------------------------------------------------------------------------- +# Configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- - # If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files - # that can be used to generate PDF. - # The default value is: NO. +# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files +# that can be used to generate PDF. +# The default value is: NO. - GENERATE_DOCBOOK = NO +GENERATE_DOCBOOK = NO - # The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put. - # If a relative path is entered the value of OUTPUT_DIRECTORY will be put in - # front of it. - # The default directory is: docbook. - # This tag requires that the tag GENERATE_DOCBOOK is set to YES. +# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in +# front of it. +# The default directory is: docbook. +# This tag requires that the tag GENERATE_DOCBOOK is set to YES. - DOCBOOK_OUTPUT = docbook +DOCBOOK_OUTPUT = docbook - #--------------------------------------------------------------------------- - # Configuration options for the AutoGen Definitions output - #--------------------------------------------------------------------------- +#--------------------------------------------------------------------------- +# Configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- - # If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an - # AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures - # the structure of the code including all documentation. Note that this feature - # is still experimental and incomplete at the moment. - # The default value is: NO. +# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an +# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# the structure of the code including all documentation. Note that this feature +# is still experimental and incomplete at the moment. +# The default value is: NO. - GENERATE_AUTOGEN_DEF = NO +GENERATE_AUTOGEN_DEF = NO - #--------------------------------------------------------------------------- - # Configuration options related to the Perl module output - #--------------------------------------------------------------------------- - - # If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module - # file that captures the structure of the code including all documentation. - # - # Note that this feature is still experimental and incomplete at the moment. - # The default value is: NO. +#--------------------------------------------------------------------------- +# Configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module +# file that captures the structure of the code including all documentation. +# +# Note that this feature is still experimental and incomplete at the moment. +# The default value is: NO. - GENERATE_PERLMOD = NO - - # If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary - # Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI - # output from the Perl module output. - # The default value is: NO. - # This tag requires that the tag GENERATE_PERLMOD is set to YES. +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary +# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI +# output from the Perl module output. +# The default value is: NO. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. - PERLMOD_LATEX = NO +PERLMOD_LATEX = NO - # If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely - # formatted so it can be parsed by a human reader. This is useful if you want to - # understand what is going on. On the other hand, if this tag is set to NO, the - # size of the Perl module output will be much smaller and Perl will parse it - # just the same. - # The default value is: YES. - # This tag requires that the tag GENERATE_PERLMOD is set to YES. - - PERLMOD_PRETTY = YES +# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely +# formatted so it can be parsed by a human reader. This is useful if you want to +# understand what is going on. On the other hand, if this tag is set to NO, the +# size of the Perl module output will be much smaller and Perl will parse it +# just the same. +# The default value is: YES. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_PRETTY = YES - # The names of the make variables in the generated doxyrules.make file are - # prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful - # so different doxyrules.make files included by the same Makefile don't - # overwrite each other's variables. - # This tag requires that the tag GENERATE_PERLMOD is set to YES. +# The names of the make variables in the generated doxyrules.make file are +# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful +# so different doxyrules.make files included by the same Makefile don't +# overwrite each other's variables. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. - PERLMOD_MAKEVAR_PREFIX = +PERLMOD_MAKEVAR_PREFIX = - #--------------------------------------------------------------------------- - # Configuration options related to the preprocessor - #--------------------------------------------------------------------------- +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- - # If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all - # C-preprocessor directives found in the sources and include files. - # The default value is: YES. +# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all +# C-preprocessor directives found in the sources and include files. +# The default value is: YES. - ENABLE_PREPROCESSING = YES - - # If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names - # in the source code. If set to NO, only conditional compilation will be - # performed. Macro expansion can be done in a controlled way by setting - # EXPAND_ONLY_PREDEF to YES. - # The default value is: NO. - # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - - MACRO_EXPANSION = YES +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names +# in the source code. If set to NO, only conditional compilation will be +# performed. Macro expansion can be done in a controlled way by setting +# EXPAND_ONLY_PREDEF to YES. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +MACRO_EXPANSION = YES - # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then - # the macro expansion is limited to the macros specified with the PREDEFINED and - # EXPAND_AS_DEFINED tags. - # The default value is: NO. - # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then +# the macro expansion is limited to the macros specified with the PREDEFINED and +# EXPAND_AS_DEFINED tags. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - EXPAND_ONLY_PREDEF = NO +EXPAND_ONLY_PREDEF = NO - # If the SEARCH_INCLUDES tag is set to YES, the include files in the - # INCLUDE_PATH will be searched if a #include is found. - # The default value is: YES. - # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. +# If the SEARCH_INCLUDES tag is set to YES, the include files in the +# INCLUDE_PATH will be searched if a #include is found. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - SEARCH_INCLUDES = YES - - # The INCLUDE_PATH tag can be used to specify one or more directories that - # contain include files that are not input files but should be processed by the - # preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of - # RECURSIVE has no effect here. - # This tag requires that the tag SEARCH_INCLUDES is set to YES. +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by the +# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of +# RECURSIVE has no effect here. +# This tag requires that the tag SEARCH_INCLUDES is set to YES. - INCLUDE_PATH = - - # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard - # patterns (like *.h and *.hpp) to filter out the header-files in the - # directories. If left blank, the patterns specified with FILE_PATTERNS will be - # used. - # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will be +# used. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - INCLUDE_FILE_PATTERNS = - - # The PREDEFINED tag can be used to specify one or more macro names that are - # defined before the preprocessor is started (similar to the -D option of e.g. - # gcc). The argument of the tag is a list of macros of the form: name or - # name=definition (no spaces). If the definition and the "=" are omitted, "=1" - # is assumed. To prevent a macro definition from being undefined via #undef or - # recursively expanded use the := operator instead of the = operator. - # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that are +# defined before the preprocessor is started (similar to the -D option of e.g. +# gcc). The argument of the tag is a list of macros of the form: name or +# name=definition (no spaces). If the definition and the "=" are omitted, "=1" +# is assumed. To prevent a macro definition from being undefined via #undef or +# recursively expanded use the := operator instead of the = operator. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. PREDEFINED = DOXYGEN_IGNORE \ __declspec(x)= \ @@ -2307,7 +2307,7 @@ BEGIN_SDV_INTERFACE_MAP()= \ SDV_INTERFACE_ENTRY(x)= \ END_SDV_INTERFACE_MAP()= \ SDV_INTERFACE_USE_NAMESPACE(x)= \ -SDV_INTERFACE_ENTRY_MEMBER(x)= \ +SDV_INTERFACE_ENTRY_MEMBER(x, y)= \ SDV_INTERFACE_ENTRY_INDIRECT(x, y)= \ SDV_INTERFACE_DENY_ENTRY(x)= \ SDV_INTERFACE_CHAIN_BASE(x)= \ @@ -2317,94 +2317,119 @@ SDV_INTERFACE_SET_SECTION_CONDITION(x, y)= \ SDV_INTERFACE_SET_SECTION(x)= \ SDV_INTERFACE_DEFAULT_SECTION()= \ SDV_INTERFACE_SECTION(x)= \ +SDV_CONCAT_IMPL(a, b)= \ +SDV_CONCAT(a, b)= \ +SDV_UNIQUE_VAR(prefix)= \ +BEGIN_SDV_LABEL_MAP(enum_type)= \ +SDV_LABEL_ENTRY(enum_element, name_string)= \ +END_SDV_LABEL_MAP()= \ +BEGIN_SDV_PARAM_MAP()= \ +END_SDV_PARAM_MAP()= \ +SDV_PARAM_GROUP(group_string)= \ +SDV_PARAM_NO_GROUP()= \ +SDV_PARAM_RESET_ATTRIBUTES()= \ +SDV_PARAM_SET_READONLY()= \ +SDV_PARAM_RESET_READONLY()= \ +SDV_PARAM_ENABLE_LOCKING()= \ +SDV_PARAM_DISABLE_LOCKING()= \ +SDV_PARAM_SET_TEMPORARY()= \ +SDV_PARAM_RESET_TEMPORARY()= \ +SDV_PARAM_ENTRY(var, name_string, default_val, unit_string, description_string)= \ +SDV_PARAM_NUMBER_ENTRY(var, name_string, default_val, low_limit, high_limit, unit_string, description_string)= \ +SDV_PARAM_ENUM_ENTRY(var, name_string, default_val, description_string)= \ +SDV_PARAM_STRING_ENTRY(var, name_string, default_val, pattern_string, unit_string, description_string)= \ +SDV_PARAM_PATH_ENTRY(var, name_string, default_val, description_string)= \ +SDV_PARAM_BITMASK_ENTRY(TEnum, var, name_string, default_val, description_string)= \ +SDV_PARAM_CHAIN_BASE(base_class)= \ +SDV_PARAM_CHAIN_MEMBER(member)= \ DEFINE_SDV_OBJECT(x)= \ switch= - # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this - # tag can be used to specify a list of macro names that should be expanded. The - # macro definition that is found in the sources will be used. Use the PREDEFINED - # tag if you want to use a different macro definition that overrules the - # definition found in the source code. - # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this +# tag can be used to specify a list of macro names that should be expanded. The +# macro definition that is found in the sources will be used. Use the PREDEFINED +# tag if you want to use a different macro definition that overrules the +# definition found in the source code. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - EXPAND_AS_DEFINED = +EXPAND_AS_DEFINED = - # If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will - # remove all references to function-like macros that are alone on a line, have - # an all uppercase name, and do not end with a semicolon. Such function macros - # are typically used for boiler-plate code, and will confuse the parser if not - # removed. - # The default value is: YES. - # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. +# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will +# remove all references to function-like macros that are alone on a line, have +# an all uppercase name, and do not end with a semicolon. Such function macros +# are typically used for boiler-plate code, and will confuse the parser if not +# removed. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - SKIP_FUNCTION_MACROS = YES +SKIP_FUNCTION_MACROS = YES - #--------------------------------------------------------------------------- - # Configuration options related to external references - #--------------------------------------------------------------------------- +#--------------------------------------------------------------------------- +# Configuration options related to external references +#--------------------------------------------------------------------------- - # The TAGFILES tag can be used to specify one or more tag files. For each tag - # file the location of the external documentation should be added. The format of - # a tag file without this location is as follows: - # TAGFILES = file1 file2 ... - # Adding location for the tag files is done as follows: - # TAGFILES = file1=loc1 "file2 = loc2" ... - # where loc1 and loc2 can be relative or absolute paths or URLs. See the - # section "Linking to external documentation" for more information about the use - # of tag files. - # Note: Each tag file must have a unique name (where the name does NOT include - # the path). If a tag file is not located in the directory in which doxygen is - # run, you must also specify the path to the tagfile here. +# The TAGFILES tag can be used to specify one or more tag files. For each tag +# file the location of the external documentation should be added. The format of +# a tag file without this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where loc1 and loc2 can be relative or absolute paths or URLs. See the +# section "Linking to external documentation" for more information about the use +# of tag files. +# Note: Each tag file must have a unique name (where the name does NOT include +# the path). If a tag file is not located in the directory in which doxygen is +# run, you must also specify the path to the tagfile here. - TAGFILES = +TAGFILES = - # When a file name is specified after GENERATE_TAGFILE, doxygen will create a - # tag file that is based on the input files it reads. See section "Linking to - # external documentation" for more information about the usage of tag files. +# When a file name is specified after GENERATE_TAGFILE, doxygen will create a +# tag file that is based on the input files it reads. See section "Linking to +# external documentation" for more information about the usage of tag files. - GENERATE_TAGFILE = +GENERATE_TAGFILE = - # If the ALLEXTERNALS tag is set to YES, all external class will be listed in - # the class index. If set to NO, only the inherited external classes will be - # listed. - # The default value is: NO. +# If the ALLEXTERNALS tag is set to YES, all external class will be listed in +# the class index. If set to NO, only the inherited external classes will be +# listed. +# The default value is: NO. - ALLEXTERNALS = NO +ALLEXTERNALS = NO - # If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed - # in the modules index. If set to NO, only the current project's groups will be - # listed. - # The default value is: YES. +# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will be +# listed. +# The default value is: YES. - EXTERNAL_GROUPS = YES +EXTERNAL_GROUPS = YES - # If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in - # the related pages index. If set to NO, only the current project's pages will - # be listed. - # The default value is: YES. +# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in +# the related pages index. If set to NO, only the current project's pages will +# be listed. +# The default value is: YES. - EXTERNAL_PAGES = YES +EXTERNAL_PAGES = YES - #--------------------------------------------------------------------------- - # Configuration options related to the dot tool - #--------------------------------------------------------------------------- +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- - # You can include diagrams made with dia in doxygen documentation. Doxygen will - # then run dia to produce the diagram and insert it in the documentation. The - # DIA_PATH tag allows you to specify the directory where the dia binary resides. - # If left empty dia is assumed to be found in the default search path. +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. - DIA_PATH = +DIA_PATH = - # If set to YES the inheritance and collaboration graphs will hide inheritance - # and usage relations if the target is undocumented or is not a class. - # The default value is: YES. +# If set to YES the inheritance and collaboration graphs will hide inheritance +# and usage relations if the target is undocumented or is not a class. +# The default value is: YES. - HIDE_UNDOC_RELATIONS = YES +HIDE_UNDOC_RELATIONS = YES - # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is - # available from the path. This tool is part of Graphviz (see: - # http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz (see: +# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent # Bell Labs. The other options in this section have no effect if this option is # set to NO # The default value is: NO. diff --git a/examples/auto_headlamp_example/autoheadlight_app/autoheadlight_console.cpp b/examples/auto_headlamp_example/autoheadlight_app/autoheadlight_console.cpp index 40a2191..13d780a 100644 --- a/examples/auto_headlamp_example/autoheadlight_app/autoheadlight_console.cpp +++ b/examples/auto_headlamp_example/autoheadlight_app/autoheadlight_console.cpp @@ -353,7 +353,7 @@ void CConsole::StartUpdateDataThread() m_bThreadStarted = true; m_bRunning = true; - m_threadReadTxSignals = std::thread(&CConsole::UpdateDataThreadFunc, this); + m_threadReadTxSignals = sdv::core::secure_thread(&CConsole::UpdateDataThreadFunc, this); } void CConsole::StopUpdateDataThread() diff --git a/examples/auto_headlamp_example/autoheadlight_app/autoheadlight_console.h b/examples/auto_headlamp_example/autoheadlight_app/autoheadlight_console.h index ab13c36..ec35250 100644 --- a/examples/auto_headlamp_example/autoheadlight_app/autoheadlight_console.h +++ b/examples/auto_headlamp_example/autoheadlight_app/autoheadlight_console.h @@ -200,33 +200,33 @@ private: */ std::string AlignString(const std::string& message, uint32_t desiredLength = 0); - mutable std::mutex m_mtxPrintToConsole; ///< Mutex to print complete message - std::thread m_threadReadTxSignals; ///< Simulation datalink thread. - bool m_bThreadStarted = false; ///< Set when initialized. - std::atomic_bool m_bRunning = false; ///< When set, the application is running. + mutable std::mutex m_mtxPrintToConsole; ///< Mutex to print complete message + sdv::core::secure_thread m_threadReadTxSignals; ///< Simulation datalink thread. + bool m_bThreadStarted = false; ///< Set when initialized. + std::atomic_bool m_bRunning = false; ///< When set, the application is running. - sdv::core::CSignal m_signalCurrentLatitude; ///< Signal Current latitude - sdv::core::CSignal m_signalCurrentLongitude; ///< Signal Current longitude - sdv::core::CSignal m_signalHeadlight; ///< Signal Headlight status + sdv::core::CSignal m_signalCurrentLatitude; ///< Signal Current latitude + sdv::core::CSignal m_signalCurrentLongitude; ///< Signal Current longitude + sdv::core::CSignal m_signalHeadlight; ///< Signal Headlight status - float m_fCurrentLongitude = 0.0f; //< Current latitude - float m_fCurrentLatitude = 0.0f; //< Current longitude - bool m_bHeadLight = true; ///< Head light + float m_fCurrentLongitude = 0.0f; //< Current latitude + float m_fCurrentLatitude = 0.0f; //< Current longitude + bool m_bHeadLight = true; ///< Head light - float m_fVehicleDeviceCurrentLatitude = 0.0f; ///< Current latitude (Vehicle Device) - float m_fVehicleDeviceCurrentLongitude = 0.0; ///< Current longitude (Vehicle Device) + float m_fVehicleDeviceCurrentLatitude = 0.0f; ///< Current latitude (Vehicle Device) + float m_fVehicleDeviceCurrentLongitude = 0.0; ///< Current longitude (Vehicle Device) - float m_fBasicServiceCurrentLatitude = 0.0f; ///< Current latitude (basic Service) - float m_fBasicServiceCurrentLongitude = 0.0; ///< Current longitude (basic Service) + float m_fBasicServiceCurrentLatitude = 0.0f; ///< Current latitude (basic Service) + float m_fBasicServiceCurrentLongitude = 0.0; ///< Current longitude (basic Service) - IAutoheadlightService* m_pIAutoheadlightComplexService = nullptr; ///< Autoheadlight Service interface pointer. + IAutoheadlightService* m_pIAutoheadlightComplexService = nullptr; ///< Autoheadlight Service interface pointer. #ifdef _WIN32 - DWORD m_dwConsoleOutMode = 0u; ///< The console mode before switching on ANSI support. - DWORD m_dwConsoleInMode = 0u; ///< The console mode before switching on ANSI support. + DWORD m_dwConsoleOutMode = 0u; ///< The console mode before switching on ANSI support. + DWORD m_dwConsoleInMode = 0u; ///< The console mode before switching on ANSI support. #elif defined __unix__ - struct termios m_sTermAttr {}; ///< The terminal attributes before disabling echo. + struct termios m_sTermAttr {}; ///< The terminal attributes before disabling echo. int m_iFileStatus = 0; ///< The file status flags for STDIN. #else #error The OS is not supported! diff --git a/examples/auto_headlamp_example/fmu_files/model.cpp b/examples/auto_headlamp_example/fmu_files/model.cpp index ad6b52c..524a9e0 100644 --- a/examples/auto_headlamp_example/fmu_files/model.cpp +++ b/examples/auto_headlamp_example/fmu_files/model.cpp @@ -94,7 +94,10 @@ bool CreateCoreServiceTomlFile(const std::string& resources) std::ofstream tomlFile("sdv_core_reloc.toml"); if (tomlFile.is_open()) { - tomlFile << "# Location of the SDV binaries and configuration files\ndirectory = \""; + tomlFile << R"toml(# Location of the SDV binaries and configuration files +[CoreLocation] +Version = 100 +Framework = ")toml"; tomlFile << resources; tomlFile << "\"\n"; tomlFile.close(); diff --git a/examples/door_demo_example/CMakeLists.txt b/examples/door_demo_example/CMakeLists.txt index cbc7141..65e2043 100644 --- a/examples/door_demo_example/CMakeLists.txt +++ b/examples/door_demo_example/CMakeLists.txt @@ -43,59 +43,63 @@ file (COPY ${PROJECT_SOURCE_DIR}/door_example_receiver.asc DESTINATION ${CMAKE_B ###################################################################################################################################################################### # Execute sdv_vss_util to create IDL files for devices and basic services. +# In this example we split into into front and rear (frontdoors_proxystub.sdv and frontdoors_proxystub.sdv) +# because all interfaces combined in doors_proxystub.sdv would be to large message("Create interface code for devices and basic services of doors example.") -execute_process(COMMAND "${SDV_VSS_UTIL}" "${PROJECT_SOURCE_DIR}/vss_doors_example.csv" "-O${PROJECT_SOURCE_DIR}/generated/" --prefixdoors --version1.0.0.1 --enable_components) +execute_process(COMMAND "${SDV_VSS_UTIL}" "${PROJECT_SOURCE_DIR}/vss_doors_front_example.csv" "-O${PROJECT_SOURCE_DIR}/generated/front/" --prefixfrontdoors --version1.0.0.1 --enable_components) +execute_process(COMMAND "${SDV_VSS_UTIL}" "${PROJECT_SOURCE_DIR}/vss_doors_rear_example.csv" "-O${PROJECT_SOURCE_DIR}/generated/rear/" --prefixreardoors --version1.0.0.1 --enable_components) message("Compiling Interface Door01Left RX") -execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/vss_files/vss_vehiclebodydooraxle_01left_vd_rx.idl" "-O${PROJECT_SOURCE_DIR}/generated/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --no_ps) -execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/vss_files/vss_vehiclebodydooraxle_01left_bs_rx.idl" "-O${PROJECT_SOURCE_DIR}/generated/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --ps_lib_namedoors_proxystub) +execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/front/vss_files/vss_vehiclebodydooraxle_01left_vd_rx.idl" "-O${PROJECT_SOURCE_DIR}/generated/front/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --no_ps) +execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/front/vss_files/vss_vehiclebodydooraxle_01left_bs_rx.idl" "-O${PROJECT_SOURCE_DIR}/generated/front/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --ps_lib_namefrontdoors_proxystub) message("Compiling Interface Door01Right RX") -execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/vss_files/vss_vehiclebodydooraxle_01right_vd_rx.idl" "-O${PROJECT_SOURCE_DIR}/generated/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --no_ps) -execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/vss_files/vss_vehiclebodydooraxle_01right_bs_rx.idl" "-O${PROJECT_SOURCE_DIR}/generated/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --ps_lib_namedoors_proxystub) - -message("Compiling Interface Door02Left RX") -execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/vss_files/vss_vehiclebodydooraxle_02left_vd_rx.idl" "-O${PROJECT_SOURCE_DIR}/generated/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --no_ps) -execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/vss_files/vss_vehiclebodydooraxle_02left_bs_rx.idl" "-O${PROJECT_SOURCE_DIR}/generated/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --ps_lib_namedoors_proxystub) - -message("Compiling Interface Door02Right RX") -execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/vss_files/vss_vehiclebodydooraxle_02right_vd_rx.idl" "-O${PROJECT_SOURCE_DIR}/generated/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --no_ps) -execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/vss_files/vss_vehiclebodydooraxle_02right_bs_rx.idl" "-O${PROJECT_SOURCE_DIR}/generated/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --ps_lib_namedoors_proxystub) - +execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/front/vss_files/vss_vehiclebodydooraxle_01right_vd_rx.idl" "-O${PROJECT_SOURCE_DIR}/generated/front/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --no_ps) +execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/front/vss_files/vss_vehiclebodydooraxle_01right_bs_rx.idl" "-O${PROJECT_SOURCE_DIR}/generated/front/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --ps_lib_namefrontdoors_proxystub) message("Compiling Interface Door01Left TX") -execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/vss_files/vss_vehiclebodydooraxle_01left_vd_tx.idl" "-O${PROJECT_SOURCE_DIR}/generated/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --no_ps) -execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/vss_files/vss_vehiclebodydooraxle_01left_bs_tx.idl" "-O${PROJECT_SOURCE_DIR}/generated/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --ps_lib_namedoors_proxystub) +execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/front/vss_files/vss_vehiclebodydooraxle_01left_vd_tx.idl" "-O${PROJECT_SOURCE_DIR}/generated/front/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --no_ps) +execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/front/vss_files/vss_vehiclebodydooraxle_01left_bs_tx.idl" "-O${PROJECT_SOURCE_DIR}/generated/front/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --ps_lib_namefrontdoors_proxystub) message("Compiling Interface Door01Right TX") -execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/vss_files/vss_vehiclebodydooraxle_01right_vd_tx.idl" "-O${PROJECT_SOURCE_DIR}/generated/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --no_ps) -execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/vss_files/vss_vehiclebodydooraxle_01right_bs_tx.idl" "-O${PROJECT_SOURCE_DIR}/generated/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --ps_lib_namedoors_proxystub) +execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/front/vss_files/vss_vehiclebodydooraxle_01right_vd_tx.idl" "-O${PROJECT_SOURCE_DIR}/generated/front/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --no_ps) +execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/front/vss_files/vss_vehiclebodydooraxle_01right_bs_tx.idl" "-O${PROJECT_SOURCE_DIR}/generated/front/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --ps_lib_namefrontdoors_proxystub) + + +message("Compiling Interface Door02Left RX") +execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/rear/vss_files/vss_vehiclebodydooraxle_02left_vd_rx.idl" "-O${PROJECT_SOURCE_DIR}/generated/rear/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --no_ps) +execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/rear/vss_files/vss_vehiclebodydooraxle_02left_bs_rx.idl" "-O${PROJECT_SOURCE_DIR}/generated/rear/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --ps_lib_namereardoors_proxystub) + +message("Compiling Interface Door02Right RX") +execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/rear/vss_files/vss_vehiclebodydooraxle_02right_vd_rx.idl" "-O${PROJECT_SOURCE_DIR}/generated/rear/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --no_ps) +execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/rear/vss_files/vss_vehiclebodydooraxle_02right_bs_rx.idl" "-O${PROJECT_SOURCE_DIR}/generated/rear/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --ps_lib_namereardoors_proxystub) message("Compiling Interface Door02Left TX") -execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/vss_files/vss_vehiclebodydooraxle_02left_vd_tx.idl" "-O${PROJECT_SOURCE_DIR}/generated/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --no_ps) -execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/vss_files/vss_vehiclebodydooraxle_02left_bs_tx.idl" "-O${PROJECT_SOURCE_DIR}/generated/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --ps_lib_namedoors_proxystub) +execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/rear/vss_files/vss_vehiclebodydooraxle_02left_vd_tx.idl" "-O${PROJECT_SOURCE_DIR}/generated/rear/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --no_ps) +execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/rear/vss_files/vss_vehiclebodydooraxle_02left_bs_tx.idl" "-O${PROJECT_SOURCE_DIR}/generated/rear/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --ps_lib_namereardoors_proxystub) message("Compiling Interface Door02Right TX") -execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/vss_files/vss_vehiclebodydooraxle_02right_vd_tx.idl" "-O${PROJECT_SOURCE_DIR}/generated/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --no_ps) -execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/vss_files/vss_vehiclebodydooraxle_02right_bs_tx.idl" "-O${PROJECT_SOURCE_DIR}/generated/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --ps_lib_namedoors_proxystub) +execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/rear/vss_files/vss_vehiclebodydooraxle_02right_vd_tx.idl" "-O${PROJECT_SOURCE_DIR}/generated/rear/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --no_ps) +execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/rear/vss_files/vss_vehiclebodydooraxle_02right_bs_tx.idl" "-O${PROJECT_SOURCE_DIR}/generated/rear/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --ps_lib_namereardoors_proxystub) # Execute the IDL compiler for the complex service to digest interface code. message("Compiling door_ifc.idl") execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/door_service/door_ifc.idl" "-O${PROJECT_SOURCE_DIR}/generated/door_service/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Iexample_service/ --ps_lib_namedoors_service_proxystub) add_subdirectory(generated/door_service/ps) -add_subdirectory(generated/vss_files/ps) +add_subdirectory(generated/front/vss_files/ps) +add_subdirectory(generated/rear/vss_files/ps) -add_subdirectory(generated/vss_files/vd_frontdoorleft) -add_subdirectory(generated/vss_files/vd_frontdoorright) -add_subdirectory(generated/vss_files/vd_reardoorleft) -add_subdirectory(generated/vss_files/vd_reardoorright) +add_subdirectory(generated/front/vss_files/vd_frontdoorleft) +add_subdirectory(generated/front/vss_files/vd_frontdoorright) +add_subdirectory(generated/front/vss_files/bs_frontdoorleft) +add_subdirectory(generated/front/vss_files/bs_frontdoorright) -add_subdirectory(generated/vss_files/bs_frontdoorleft) -add_subdirectory(generated/vss_files/bs_frontdoorright) -add_subdirectory(generated/vss_files/bs_reardoorleft) -add_subdirectory(generated/vss_files/bs_reardoorright) +add_subdirectory(generated/rear/vss_files/vd_reardoorleft) +add_subdirectory(generated/rear/vss_files/vd_reardoorright) +add_subdirectory(generated/rear/vss_files/bs_reardoorleft) +add_subdirectory(generated/rear/vss_files/bs_reardoorright) # Execute sdv_dbc_util to create data link code & FMU code. message("Create data link for doors example") @@ -167,7 +171,6 @@ else() target_link_libraries(door_external_app Rpcrt4.lib) endif() - ###################################################################################################################################################################### # create instance 3002 of door demo example ###################################################################################################################################################################### @@ -175,23 +178,25 @@ add_custom_target(vehicle_device_doors_config ALL DEPENDS can_dl_doors - doors_vd_frontdoorleft_rx - doors_vd_frontdoorright_rx - doors_vd_reardoorleft_rx - doors_vd_reardoorright_rx - COMMAND "${SDV_PACKAGER}" DIRECT_INSTALL VehicleDeviceDoors --instance3002 can_dl_doors.sdv doors_vd_frontdoorleft_rx.sdv doors_vd_frontdoorright_rx.sdv doors_vd_reardoorleft_rx.sdv doors_vd_reardoorright_rx.sdv "-I${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" --interface_config --overwrite + frontdoors_vd_frontdoorleft_rx + frontdoors_vd_frontdoorright_rx + reardoors_vd_reardoorleft_rx + reardoors_vd_reardoorright_rx + COMMAND "${SDV_PACKAGER}" DIRECT_INSTALL VehicleDeviceDoors --instance3002 can_dl_doors.sdv frontdoors_vd_frontdoorleft_rx.sdv frontdoors_vd_frontdoorright_rx.sdv reardoors_vd_reardoorleft_rx.sdv reardoors_vd_reardoorright_rx.sdv "-I${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" --interface_config --overwrite VERBATIM ) + add_custom_target(basic_service_doors_config ALL DEPENDS - doors_proxystub - doors_bs_frontdoorleft_rx - doors_bs_frontdoorright_rx - doors_bs_reardoorleft_rx - doors_bs_reardoorright_rx - COMMAND "${SDV_PACKAGER}" DIRECT_INSTALL BasicServiceDoors --instance3002 doors_proxystub.sdv doors_bs_frontdoorleft_rx.sdv doors_bs_frontdoorright_rx.sdv doors_bs_reardoorleft_rx.sdv doors_bs_reardoorright_rx.sdv "-I${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" --abstract_config --overwrite + frontdoors_proxystub + reardoors_proxystub + frontdoors_bs_frontdoorleft_rx + frontdoors_bs_frontdoorright_rx + reardoors_bs_reardoorleft_rx + reardoors_bs_reardoorright_rx + COMMAND "${SDV_PACKAGER}" DIRECT_INSTALL BasicServiceDoors --instance3002 frontdoors_proxystub.sdv reardoors_proxystub.sdv frontdoors_bs_frontdoorleft_rx.sdv frontdoors_bs_frontdoorright_rx.sdv reardoors_bs_reardoorleft_rx.sdv reardoors_bs_reardoorright_rx.sdv "-I${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" --abstract_config --overwrite VERBATIM ) diff --git a/examples/door_demo_example/config/front_left_door_example.toml b/examples/door_demo_example/config/front_left_door_example.toml index dd6699c..3019feb 100644 --- a/examples/door_demo_example/config/front_left_door_example.toml +++ b/examples/door_demo_example/config/front_left_door_example.toml @@ -2,11 +2,11 @@ Version = 100 [[Component]] -Path = "doors_vd_frontdoorleft_rx.sdv" +Path = "frontdoors_vd_frontdoorleft_rx.sdv" Class = "Vehicle.Body.Door.Axle._01.Left_Device" [[Component]] -Path = "doors_bs_frontdoorleft_rx.sdv" +Path = "frontdoors_bs_frontdoorleft_rx.sdv" Class = "Vehicle.Body.Door.Axle._01.Left_Service" diff --git a/examples/door_demo_example/config/front_right_door_example.toml b/examples/door_demo_example/config/front_right_door_example.toml index 68f14bd..bce3013 100644 --- a/examples/door_demo_example/config/front_right_door_example.toml +++ b/examples/door_demo_example/config/front_right_door_example.toml @@ -2,11 +2,11 @@ Version = 100 [[Component]] -Path = "doors_vd_frontdoorright_rx.sdv" +Path = "frontdoors_vd_frontdoorright_rx.sdv" Class = "Vehicle.Body.Door.Axle._01.Right_Device" [[Component]] -Path = "doors_bs_frontdoorright_rx.sdv" +Path = "frontdoors_bs_frontdoorright_rx.sdv" Class = "Vehicle.Body.Door.Axle._01.Right_Service" diff --git a/examples/door_demo_example/config/rear_left_door_example.toml b/examples/door_demo_example/config/rear_left_door_example.toml index 2da09dd..e26f52d 100644 --- a/examples/door_demo_example/config/rear_left_door_example.toml +++ b/examples/door_demo_example/config/rear_left_door_example.toml @@ -2,11 +2,11 @@ Version = 100 [[Component]] -Path = "doors_vd_reardoorleft_rx.sdv" +Path = "reardoors_vd_reardoorleft_rx.sdv" Class = "Vehicle.Body.Door.Axle._02.Left_Device" [[Component]] -Path = "doors_bs_reardoorleft_rx.sdv" +Path = "reardoors_bs_reardoorleft_rx.sdv" Class = "Vehicle.Body.Door.Axle._02.Left_Service" diff --git a/examples/door_demo_example/config/rear_right_door_example.toml b/examples/door_demo_example/config/rear_right_door_example.toml index 4f2069e..64acf61 100644 --- a/examples/door_demo_example/config/rear_right_door_example.toml +++ b/examples/door_demo_example/config/rear_right_door_example.toml @@ -2,9 +2,9 @@ Version = 100 [[Component]] -Path = "doors_vd_reardoorright_rx.sdv" +Path = "reardoors_vd_reardoorright_rx.sdv" Class = "Vehicle.Body.Door.Axle._02.Right_Device" [[Component]] -Path = "doors_bs_reardoorright_rx.sdv" +Path = "reardoors_bs_reardoorright_rx.sdv" Class = "Vehicle.Body.Door.Axle._02.Right_Service" diff --git a/examples/door_demo_example/door_app/console.cpp b/examples/door_demo_example/door_app/console.cpp index acd9dea..f4c3cc2 100644 --- a/examples/door_demo_example/door_app/console.cpp +++ b/examples/door_demo_example/door_app/console.cpp @@ -128,7 +128,6 @@ void CConsole::PrintHeader(const uint32_t numberOfDoors) PrintText(g_sTitle, title.c_str()); PrintText(g_sControlDescription, "Press 'X' to quit; Doors are toggled automatically"); } - } bool CConsole::PrepareDataConsumers() @@ -205,8 +204,7 @@ void CConsole::ResetSignals() // Set the cursor position at the end SetCursorPos(g_sCursor); - // Registrate for the vehicle device & basic service of the front left door. Front left door mzust exist, the others are optional - auto vehicleDevice = + auto vehicleDevice = sdv::core::GetObject("Vehicle.Body.Door.Axle._01.Left_Device").GetInterface < vss::Vehicle::Body::Door::Axle::_01 ::LeftDevice::IVSS_ReadIsOpen > (); if (vehicleDevice) vehicleDevice->UnregisterIsOpenEvent(dynamic_cast (this)); @@ -254,7 +252,7 @@ void CConsole::StartUpdateDataThread() m_bThreadStarted = true; m_bRunning = true; - m_threadReadTxSignals = std::thread(&CConsole::UpdateDataThreadFunc, this); + m_threadReadTxSignals = sdv::core::secure_thread(&CConsole::UpdateDataThreadFunc, this); } void CConsole::StopUpdateDataThread() @@ -299,21 +297,21 @@ bool CConsole::PrepareDataConsumersForStandAlone() { // Subscribe for the door and if available get TX signal. Either both exists or none of them sdv::core::CDispatchService dispatch; - m_SignalFrontLeftDoorIsOpen = dispatch.Subscribe(doors::dsLeftDoorIsOpen01, [&](sdv::any_t value) { CallbackFrontLeftDoorIsOpen(value); }); + m_SignalFrontLeftDoorIsOpen = dispatch.Subscribe(frontdoors::dsLeftDoorIsOpen01, [&](sdv::any_t value) { CallbackFrontLeftDoorIsOpen(value); }); if(m_SignalFrontLeftDoorIsOpen) - m_SignalFrontLeftDoorIsLocked = dispatch.RegisterTxSignal(doors::dsLeftLatch01, 0); + m_SignalFrontLeftDoorIsLocked = dispatch.RegisterTxSignal(frontdoors::dsLeftLatch01, 0); - m_SignalFrontRightDoorIsOpen = dispatch.Subscribe(doors::dsRightDoorIsOpen01, [&](sdv::any_t value) { CallbackFrontRightDoorIsOpen(value); }); + m_SignalFrontRightDoorIsOpen = dispatch.Subscribe(frontdoors::dsRightDoorIsOpen01, [&](sdv::any_t value) { CallbackFrontRightDoorIsOpen(value); }); if(m_SignalFrontRightDoorIsOpen) - m_SignalFrontRightDoorIsLocked = dispatch.RegisterTxSignal(doors::dsRightLatch01, 0); + m_SignalFrontRightDoorIsLocked = dispatch.RegisterTxSignal(frontdoors::dsRightLatch01, 0); - m_SignalRearLeftDoorIsOpen = dispatch.Subscribe(doors::dsLeftDoorIsOpen02, [&](sdv::any_t value) {CallbackRearLeftDoorIsOpen(value); }); + m_SignalRearLeftDoorIsOpen = dispatch.Subscribe(reardoors::dsLeftDoorIsOpen02, [&](sdv::any_t value) {CallbackRearLeftDoorIsOpen(value); }); if(m_SignalRearLeftDoorIsOpen) - m_SignalRearLeftDoorIsLocked = dispatch.RegisterTxSignal(doors::dsLeftLatch02, 0); + m_SignalRearLeftDoorIsLocked = dispatch.RegisterTxSignal(reardoors::dsLeftLatch02, 0); - m_SignalRearRightDoorIsOpen = dispatch.Subscribe(doors::dsRightDoorIsOpen02, [&](sdv::any_t value) { CallbackRearRightDoorIsOpen(value); }); + m_SignalRearRightDoorIsOpen = dispatch.Subscribe(reardoors::dsRightDoorIsOpen02, [&](sdv::any_t value) { CallbackRearRightDoorIsOpen(value); }); if(m_SignalRearRightDoorIsOpen) - m_SignalRearRightDoorIsLocked = dispatch.RegisterTxSignal(doors::dsRightLatch02, 0); + m_SignalRearRightDoorIsLocked = dispatch.RegisterTxSignal(reardoors::dsRightLatch02, 0); // Validate: Either both exists or none of them if (m_SignalFrontLeftDoorIsOpen != m_SignalFrontLeftDoorIsLocked) @@ -337,7 +335,7 @@ bool CConsole::PrepareDataConsumersForStandAlone() return false; } - // Registrate for the vehicle device & basic service of the front left door. Front left door mzust exist, the others are optional + // Register for the vehicle device & basic service of the front left door. Front left door must exist, the others are optional auto vehicleDevice = sdv::core::GetObject("Vehicle.Body.Door.Axle._01.Left_Device").GetInterface(); if (!vehicleDevice) { diff --git a/examples/door_demo_example/door_app/door_application.cpp b/examples/door_demo_example/door_app/door_application.cpp index 1f0ee6a..4015879 100644 --- a/examples/door_demo_example/door_app/door_application.cpp +++ b/examples/door_demo_example/door_app/door_application.cpp @@ -9,7 +9,8 @@ ********************************************************************************/ #include "../door_app/include/door_application.h" -#include "../generated/vss_files/signal_identifier.h" +#include "../generated/front/vss_files/signal_identifier.h" +#include "../generated/rear/vss_files/signal_identifier.h" #ifdef _WIN32 #include // Needed for _kbhit @@ -61,23 +62,23 @@ bool CDoorControl::RegisterSignals() { sdv::core::CDispatchService dispatch; - m_SignalFrontLeftDoorIsOpen = dispatch.RegisterRxSignal(doors::dsLeftDoorIsOpen01); - m_SignalFrontLeftDoorIsLocked = dispatch.RegisterTxSignal(doors::dsLeftLatch01, 0); + m_SignalFrontLeftDoorIsOpen = dispatch.RegisterRxSignal(frontdoors::dsLeftDoorIsOpen01); + m_SignalFrontLeftDoorIsLocked = dispatch.RegisterTxSignal(frontdoors::dsLeftLatch01, 0); if (m_iNumberOfDoors > 1) { - m_SignalFrontRightDoorIsOpen = dispatch.RegisterRxSignal(doors::dsRightDoorIsOpen01); - m_SignalFrontRightDoorIsLocked = dispatch.RegisterTxSignal(doors::dsRightLatch01, 0); + m_SignalFrontRightDoorIsOpen = dispatch.RegisterRxSignal(frontdoors::dsRightDoorIsOpen01); + m_SignalFrontRightDoorIsLocked = dispatch.RegisterTxSignal(frontdoors::dsRightLatch01, 0); if (m_iNumberOfDoors > 2) { - m_SignalRearLeftDoorIsOpen = dispatch.RegisterRxSignal(doors::dsLeftDoorIsOpen02); - m_SignalRearLeftDoorIsLocked = dispatch.RegisterTxSignal(doors::dsLeftLatch02, 0); + m_SignalRearLeftDoorIsOpen = dispatch.RegisterRxSignal(reardoors::dsLeftDoorIsOpen02); + m_SignalRearLeftDoorIsLocked = dispatch.RegisterTxSignal(reardoors::dsLeftLatch02, 0); if (m_iNumberOfDoors > 3) { - m_SignalRearRightDoorIsOpen = dispatch.RegisterRxSignal(doors::dsRightDoorIsOpen02); - m_SignalRearRightDoorIsLocked = dispatch.RegisterTxSignal(doors::dsRightLatch02, 0); + m_SignalRearRightDoorIsOpen = dispatch.RegisterRxSignal(reardoors::dsRightDoorIsOpen02); + m_SignalRearRightDoorIsLocked = dispatch.RegisterTxSignal(reardoors::dsRightLatch02, 0); } } } diff --git a/examples/door_demo_example/door_app/door_extern_application.cpp b/examples/door_demo_example/door_app/door_extern_application.cpp index 6cbdfbe..3189c1f 100644 --- a/examples/door_demo_example/door_app/door_extern_application.cpp +++ b/examples/door_demo_example/door_app/door_extern_application.cpp @@ -9,7 +9,8 @@ ********************************************************************************/ #include "../door_app/include/door_extern_application.h" -#include "../generated/vss_files/signal_identifier.h" +#include "../generated/front/vss_files/signal_identifier.h" +#include "../generated/rear/vss_files/signal_identifier.h" #ifdef _WIN32 #include // Needed for _kbhit diff --git a/examples/door_demo_example/door_app/include/console.h b/examples/door_demo_example/door_app/include/console.h index b88e257..bfe2e92 100644 --- a/examples/door_demo_example/door_app/include/console.h +++ b/examples/door_demo_example/door_app/include/console.h @@ -19,15 +19,16 @@ #include #include #include -#include "../../generated/vss_files/signal_identifier.h" +#include "../../generated/front/vss_files/signal_identifier.h" +#include "../../generated/rear/vss_files/signal_identifier.h" #include #include -#include "../../generated/vss_files/vss_vehiclebodydooraxle_01left_vd_rx.h" -#include "../../generated/vss_files/vss_vehiclebodydooraxle_01left_bs_rx.h" -#include "../../generated/vss_files/vss_vehiclebodydooraxle_01right_bs_rx.h" -#include "../../generated/vss_files/vss_vehiclebodydooraxle_02left_bs_rx.h" -#include "../../generated/vss_files/vss_vehiclebodydooraxle_02right_bs_rx.h" +#include "../../generated/front/vss_files/vss_vehiclebodydooraxle_01left_vd_rx.h" +#include "../../generated/front/vss_files/vss_vehiclebodydooraxle_01left_bs_rx.h" +#include "../../generated/front/vss_files/vss_vehiclebodydooraxle_01right_bs_rx.h" +#include "../../generated/rear/vss_files/vss_vehiclebodydooraxle_02left_bs_rx.h" +#include "../../generated/rear/vss_files/vss_vehiclebodydooraxle_02right_bs_rx.h" #ifdef __unix__ #include // Needed for tcgetattr and fcntl @@ -215,41 +216,41 @@ private: */ std::string AlignString(const std::string& message, uint32_t desiredLength = 0); - mutable std::mutex m_mtxPrintToConsole; ///< Mutex to print complete message - bool m_bThreadStarted = false; ///< Set when initialized. - std::atomic_bool m_bRunning = false; ///< When set, the application is running. - bool m_isExternalApp = false; ///< True when we have an external application + mutable std::mutex m_mtxPrintToConsole; ///< Mutex to print complete message + bool m_bThreadStarted = false; ///< Set when initialized. + std::atomic_bool m_bRunning = false; ///< When set, the application is running. + bool m_isExternalApp = false; ///< True when we have an external application - std::thread m_threadReadTxSignals; ///< Simulation datalink thread. + sdv::core::secure_thread m_threadReadTxSignals; ///< Simulation datalink thread. - sdv::core::CSignal m_SignalFrontLeftDoorIsOpen; ///< Front Left Door signal (RX input) - open / closed - sdv::core::CSignal m_SignalFrontRightDoorIsOpen; ///< Front Right Door signal (RX input) - open / closed - sdv::core::CSignal m_SignalRearLeftDoorIsOpen; ///< Rear Left Door signal (RX input) - open / closed - sdv::core::CSignal m_SignalRearRightDoorIsOpen; ///< Rear Right Door signal (RX input) - open / closed + sdv::core::CSignal m_SignalFrontLeftDoorIsOpen; ///< Front Left Door signal (RX input) - open / closed + sdv::core::CSignal m_SignalFrontRightDoorIsOpen; ///< Front Right Door signal (RX input) - open / closed + sdv::core::CSignal m_SignalRearLeftDoorIsOpen; ///< Rear Left Door signal (RX input) - open / closed + sdv::core::CSignal m_SignalRearRightDoorIsOpen; ///< Rear Right Door signal (RX input) - open / closed - bool m_FrontLeftDoorIsOpen = false; ///< Front Left Door value (RX input signal) - open / closed - bool m_FrontRightDoorIsOpen = false; ///< Front Right Door value (RX input signal) - open / closed - bool m_RearLeftDoorIsOpen = false; ///< Rear Left Door value (RX input signal) - open / closed - bool m_RearRightDoorIsOpen = false; ///< Rear Right Door value (RX input signal) - open / closed + bool m_FrontLeftDoorIsOpen = false; ///< Front Left Door value (RX input signal) - open / closed + bool m_FrontRightDoorIsOpen = false; ///< Front Right Door value (RX input signal) - open / closed + bool m_RearLeftDoorIsOpen = false; ///< Rear Left Door value (RX input signal) - open / closed + bool m_RearRightDoorIsOpen = false; ///< Rear Right Door value (RX input signal) - open / closed - sdv::core::CSignal m_SignalFrontLeftDoorIsLocked; ///< Front Left Door signal (TX output) - locked / unlocked - sdv::core::CSignal m_SignalFrontRightDoorIsLocked; ///< Front Right Door signal (TX output) - locked / unlocked - sdv::core::CSignal m_SignalRearLeftDoorIsLocked; ///< Rear Left Door signal (TX output) - locked / unlocked - sdv::core::CSignal m_SignalRearRightDoorIsLocked; ///< Rear Right Door signal (TX output) - locked / unlocked + sdv::core::CSignal m_SignalFrontLeftDoorIsLocked; ///< Front Left Door signal (TX output) - locked / unlocked + sdv::core::CSignal m_SignalFrontRightDoorIsLocked; ///< Front Right Door signal (TX output) - locked / unlocked + sdv::core::CSignal m_SignalRearLeftDoorIsLocked; ///< Rear Left Door signal (TX output) - locked / unlocked + sdv::core::CSignal m_SignalRearRightDoorIsLocked; ///< Rear Right Door signal (TX output) - locked / unlocked - bool m_FrontLeftDoorIsLocked = false; ///< Front Left Door value (TX output) - locked / unlocked - bool m_FrontRightDoorIsLocked = false; ///< Front Right Door value (TX output) - locked / unlocked - bool m_RearLeftDoorIsLocked = false; ///< Rear Left Door value (TX output) - locked / unlocked - bool m_RearRightDoorIsLocked = false; ///< Rear Right Door value (TX output) - locked / unlocked + bool m_FrontLeftDoorIsLocked = false; ///< Front Left Door value (TX output) - locked / unlocked + bool m_FrontRightDoorIsLocked = false; ///< Front Right Door value (TX output) - locked / unlocked + bool m_RearLeftDoorIsLocked = false; ///< Rear Left Door value (TX output) - locked / unlocked + bool m_RearRightDoorIsLocked = false; ///< Rear Right Door value (TX output) - locked / unlocked - IDoorService* m_pDoorService = nullptr; ///< Door service interface pointer. + IDoorService* m_pDoorService = nullptr; ///< Door service interface pointer. #ifdef _WIN32 - DWORD m_dwConsoleOutMode = 0u; ///< The console mode before switching on ANSI support. - DWORD m_dwConsoleInMode = 0u; ///< The console mode before switching on ANSI support. + DWORD m_dwConsoleOutMode = 0u; ///< The console mode before switching on ANSI support. + DWORD m_dwConsoleInMode = 0u; ///< The console mode before switching on ANSI support. #elif defined __unix__ - struct termios m_sTermAttr{}; ///< The terminal attributes before disabling echo. - int m_iFileStatus = 0; ///< The file status flags for STDIN. + struct termios m_sTermAttr{}; ///< The terminal attributes before disabling echo. + int m_iFileStatus = 0; ///< The file status flags for STDIN. #else #error The OS is not supported! #endif diff --git a/examples/door_demo_example/door_service/complex_service.h b/examples/door_demo_example/door_service/complex_service.h index c3b56fd..f25d963 100644 --- a/examples/door_demo_example/door_service/complex_service.h +++ b/examples/door_demo_example/door_service/complex_service.h @@ -19,14 +19,14 @@ #include // VSS interfaces - located in ../generated/vss_files/ -#include "../generated/vss_files/vss_vehiclebodydooraxle_01left_bs_rx.h" -#include "../generated/vss_files/vss_vehiclebodydooraxle_01left_bs_tx.h" -#include "../generated/vss_files/vss_vehiclebodydooraxle_01right_bs_rx.h" -#include "../generated/vss_files/vss_vehiclebodydooraxle_01right_bs_tx.h" -#include "../generated/vss_files/vss_vehiclebodydooraxle_02left_bs_rx.h" -#include "../generated/vss_files/vss_vehiclebodydooraxle_02left_bs_tx.h" -#include "../generated/vss_files/vss_vehiclebodydooraxle_02right_bs_rx.h" -#include "../generated/vss_files/vss_vehiclebodydooraxle_02right_bs_tx.h" +#include "../generated/front/vss_files/vss_vehiclebodydooraxle_01left_bs_rx.h" +#include "../generated/front/vss_files/vss_vehiclebodydooraxle_01left_bs_tx.h" +#include "../generated/front/vss_files/vss_vehiclebodydooraxle_01right_bs_rx.h" +#include "../generated/front/vss_files/vss_vehiclebodydooraxle_01right_bs_tx.h" +#include "../generated/rear/vss_files/vss_vehiclebodydooraxle_02left_bs_rx.h" +#include "../generated/rear/vss_files/vss_vehiclebodydooraxle_02left_bs_tx.h" +#include "../generated/rear/vss_files/vss_vehiclebodydooraxle_02right_bs_rx.h" +#include "../generated/rear/vss_files/vss_vehiclebodydooraxle_02right_bs_tx.h" #include "lock_doors_thread.h" diff --git a/examples/door_demo_example/door_service/lock_doors_thread.h b/examples/door_demo_example/door_service/lock_doors_thread.h index 510c138..0aa2cd3 100644 --- a/examples/door_demo_example/door_service/lock_doors_thread.h +++ b/examples/door_demo_example/door_service/lock_doors_thread.h @@ -40,7 +40,7 @@ public: m_timeIn100MilliSeconds = timeSpan; m_counter = 0; m_running = true; - m_thread = std::thread(&LockDoorsThread::run, this); + m_thread = sdv::core::secure_thread(&LockDoorsThread::run, this); } /** @@ -94,11 +94,11 @@ public: m_running = false; } - std::function m_callback; ///< callback function - std::atomic m_running; ///< status if loop is running - std::thread m_thread; ///< timer thread - uint32_t m_counter; ///< loop counter - uint32_t m_timeIn100MilliSeconds; ///< time before the callback will be executed + std::function m_callback; ///< callback function + std::atomic m_running; ///< status if loop is running + sdv::core::secure_thread m_thread; ///< timer thread + uint32_t m_counter; ///< loop counter + uint32_t m_timeIn100MilliSeconds; ///< time before the callback will be executed }; #endif // !define LOCK_DOORS_THREAD_H diff --git a/examples/door_demo_example/fmu_Doors2ExampleFMU/Doors2ExampleFMU/model.cpp b/examples/door_demo_example/fmu_Doors2ExampleFMU/Doors2ExampleFMU/model.cpp index fa4b743..d77b2e3 100644 --- a/examples/door_demo_example/fmu_Doors2ExampleFMU/Doors2ExampleFMU/model.cpp +++ b/examples/door_demo_example/fmu_Doors2ExampleFMU/Doors2ExampleFMU/model.cpp @@ -100,7 +100,10 @@ bool CreateCoreServiceTomlFile(const std::string& resources) std::ofstream tomlFile("sdv_core_reloc.toml"); if (tomlFile.is_open()) { - tomlFile << "# Location of the SDV binaries and configuration files\ndirectory = \""; + tomlFile << R"toml(# Location of the SDV binaries and configuration files +[CoreLocation] +Version = 100 +Framework = ")toml"; tomlFile << resources; tomlFile << "\"\n"; tomlFile.close(); diff --git a/examples/door_demo_example/fmu_Doors2ExampleFMU/Doors2ExampleFMU/resources/front_left_door_example.toml b/examples/door_demo_example/fmu_Doors2ExampleFMU/Doors2ExampleFMU/resources/front_left_door_example.toml index 63c92da..e16120b 100644 --- a/examples/door_demo_example/fmu_Doors2ExampleFMU/Doors2ExampleFMU/resources/front_left_door_example.toml +++ b/examples/door_demo_example/fmu_Doors2ExampleFMU/Doors2ExampleFMU/resources/front_left_door_example.toml @@ -2,11 +2,11 @@ Version = 100 [[Component]] -Path = "doors_vd_frontdoorleft_rx.sdv" +Path = "frontdoors_vd_frontdoorleft_rx.sdv" Class = "Vehicle.Body.Door.Axle._01.Left_Device" [[Component]] -Path = "doors_bs_frontdoorleft_rx.sdv" +Path = "frontdoors_bs_frontdoorleft_rx.sdv" Class = "Vehicle.Body.Door.Axle._01.Left_Service" diff --git a/examples/door_demo_example/fmu_Doors2ExampleFMU/Doors2ExampleFMU/resources/front_right_door_example.toml b/examples/door_demo_example/fmu_Doors2ExampleFMU/Doors2ExampleFMU/resources/front_right_door_example.toml index 2aca0cd..05298a2 100644 --- a/examples/door_demo_example/fmu_Doors2ExampleFMU/Doors2ExampleFMU/resources/front_right_door_example.toml +++ b/examples/door_demo_example/fmu_Doors2ExampleFMU/Doors2ExampleFMU/resources/front_right_door_example.toml @@ -2,10 +2,10 @@ Version = 100 [[Component]] -Path = "doors_vd_frontdoorright_rx.sdv" +Path = "frontdoors_vd_frontdoorright_rx.sdv" Class = "Vehicle.Body.Door.Axle._01.Right_Device" [[Component]] -Path = "doors_bs_frontdoorright_rx.sdv" +Path = "frontdoors_bs_frontdoorright_rx.sdv" Class = "Vehicle.Body.Door.Axle._01.Right_Service" diff --git a/examples/door_demo_example/fmu_Doors4ExampleFMU/Doors4ExampleFMU/model.cpp b/examples/door_demo_example/fmu_Doors4ExampleFMU/Doors4ExampleFMU/model.cpp index 7dd9d35..77892c7 100644 --- a/examples/door_demo_example/fmu_Doors4ExampleFMU/Doors4ExampleFMU/model.cpp +++ b/examples/door_demo_example/fmu_Doors4ExampleFMU/Doors4ExampleFMU/model.cpp @@ -125,7 +125,10 @@ bool CreateCoreServiceTomlFile(const std::string& resources) std::ofstream tomlFile("sdv_core_reloc.toml"); if (tomlFile.is_open()) { - tomlFile << "# Location of the SDV binaries and configuration files\ndirectory = \""; + tomlFile << R"toml(# Location of the SDV binaries and configuration files +[CoreLocation] +Version = 100 +Framework = ")toml"; tomlFile << resources; tomlFile << "\"\n"; tomlFile.close(); diff --git a/examples/door_demo_example/fmu_Doors4ExampleFMU/Doors4ExampleFMU/resources/front_left_door_example.toml b/examples/door_demo_example/fmu_Doors4ExampleFMU/Doors4ExampleFMU/resources/front_left_door_example.toml index e3162ea..142c14a 100644 --- a/examples/door_demo_example/fmu_Doors4ExampleFMU/Doors4ExampleFMU/resources/front_left_door_example.toml +++ b/examples/door_demo_example/fmu_Doors4ExampleFMU/Doors4ExampleFMU/resources/front_left_door_example.toml @@ -2,11 +2,11 @@ Version = 100 [[Component]] -Path = "doors_vd_frontdoorleft_rx.sdv" +Path = "frontdoors_vd_frontdoorleft_rx.sdv" Class = "Vehicle.Body.Door.Axle._01.Left_Device" [[Component]] -Path = "doors_bs_frontdoorleft_rx.sdv" +Path = "frontdoors_bs_frontdoorleft_rx.sdv" Class = "Vehicle.Body.Door.Axle._01.Left_Service" diff --git a/examples/door_demo_example/fmu_Doors4ExampleFMU/Doors4ExampleFMU/resources/front_right_door_example.toml b/examples/door_demo_example/fmu_Doors4ExampleFMU/Doors4ExampleFMU/resources/front_right_door_example.toml index 2aca0cd..05298a2 100644 --- a/examples/door_demo_example/fmu_Doors4ExampleFMU/Doors4ExampleFMU/resources/front_right_door_example.toml +++ b/examples/door_demo_example/fmu_Doors4ExampleFMU/Doors4ExampleFMU/resources/front_right_door_example.toml @@ -2,10 +2,10 @@ Version = 100 [[Component]] -Path = "doors_vd_frontdoorright_rx.sdv" +Path = "frontdoors_vd_frontdoorright_rx.sdv" Class = "Vehicle.Body.Door.Axle._01.Right_Device" [[Component]] -Path = "doors_bs_frontdoorright_rx.sdv" +Path = "frontdoors_bs_frontdoorright_rx.sdv" Class = "Vehicle.Body.Door.Axle._01.Right_Service" diff --git a/examples/door_demo_example/fmu_Doors4ExampleFMU/Doors4ExampleFMU/resources/rear_left_door_example.toml b/examples/door_demo_example/fmu_Doors4ExampleFMU/Doors4ExampleFMU/resources/rear_left_door_example.toml index 23e6935..b4a5871 100644 --- a/examples/door_demo_example/fmu_Doors4ExampleFMU/Doors4ExampleFMU/resources/rear_left_door_example.toml +++ b/examples/door_demo_example/fmu_Doors4ExampleFMU/Doors4ExampleFMU/resources/rear_left_door_example.toml @@ -2,11 +2,11 @@ Version = 100 [[Component]] -Path = "doors_vd_reardoorleft_rx.sdv" +Path = "reardoors_vd_reardoorleft_rx.sdv" Class = "Vehicle.Body.Door.Axle._02.Left_Device" [[Component]] -Path = "doors_bs_reardoorleft_rx.sdv" +Path = "reardoors_bs_reardoorleft_rx.sdv" Class = "Vehicle.Body.Door.Axle._02.Left_Service" diff --git a/examples/door_demo_example/fmu_Doors4ExampleFMU/Doors4ExampleFMU/resources/rear_right_door_example.toml b/examples/door_demo_example/fmu_Doors4ExampleFMU/Doors4ExampleFMU/resources/rear_right_door_example.toml index 4f2069e..64acf61 100644 --- a/examples/door_demo_example/fmu_Doors4ExampleFMU/Doors4ExampleFMU/resources/rear_right_door_example.toml +++ b/examples/door_demo_example/fmu_Doors4ExampleFMU/Doors4ExampleFMU/resources/rear_right_door_example.toml @@ -2,9 +2,9 @@ Version = 100 [[Component]] -Path = "doors_vd_reardoorright_rx.sdv" +Path = "reardoors_vd_reardoorright_rx.sdv" Class = "Vehicle.Body.Door.Axle._02.Right_Device" [[Component]] -Path = "doors_bs_reardoorright_rx.sdv" +Path = "reardoors_bs_reardoorright_rx.sdv" Class = "Vehicle.Body.Door.Axle._02.Right_Service" diff --git a/examples/door_demo_example/vss_doors_example.csv b/examples/door_demo_example/vss_doors_front_example.csv similarity index 53% rename from examples/door_demo_example/vss_doors_example.csv rename to examples/door_demo_example/vss_doors_front_example.csv index 005566f..f3e4e6e 100644 --- a/examples/door_demo_example/vss_doors_example.csv +++ b/examples/door_demo_example/vss_doors_front_example.csv @@ -2,17 +2,9 @@ ;;;;;;; VD;FrontDoorLeft;IsOpen;leftDoorIsOpen01;Vehicle.Body.Door.Axle.01.Left;RX;boolean;CAN_Input_L1.Door01LeftIsOpen VD;FrontDoorRight;IsOpen;rightDoorIsOpen01;Vehicle.Body.Door.Axle.01.Right;RX;boolean;CAN_Input_R1.Door01RightIsOpen -VD;RearDoorLeft;IsOpen;leftDoorIsOpen02;Vehicle.Body.Door.Axle.02.Left;RX;boolean;CAN_Input_L2.Door02LeftIsOpen -VD;RearDoorRight;IsOpen;rightDoorIsOpen02;Vehicle.Body.Door.Axle.02.Right;RX;boolean;CAN_Input_R2.Door02RightIsOpen VD;FrontDoorLeft;Lock;leftLatch01;Vehicle.Body.Door.Axle.01.Left;TX;boolean;CAN_Output.LockDoor01Left VD;FrontDoorRight;Lock;rightLatch01;Vehicle.Body.Door.Axle.01.Right;TX;boolean;CAN_Output.LockDoor01Right -VD;RearDoorLeft;Lock;leftLatch02;Vehicle.Body.Door.Axle.02.Left;TX;boolean;CAN_Output.LockDoor02Left -VD;RearDoorRight;Lock;rightLatch02;Vehicle.Body.Door.Axle.02.Right;TX;boolean;CAN_Output.LockDoor02Right BS;FrontDoorLeft;IsOpenL1;leftDoorIsOpen01;Vehicle.Body.Door.Axle.01.Left;RX;boolean;Vehicle.Body.Door.Axle.01.Left BS;FrontDoorRight;IsOpenR1;rightDoorIsOpen01;Vehicle.Body.Door.Axle.01.Right;RX;boolean;Vehicle.Body.Door.Axle.01.Right -BS;RearDoorLeft;IsOpenL2;leftDoorIsOpen02;Vehicle.Body.Door.Axle.02.Left;RX;boolean;Vehicle.Body.Door.Axle.02.Left -BS;RearDoorRight;IsOpenR2;rightDoorIsOpen02;Vehicle.Body.Door.Axle.02.Right;RX;boolean;Vehicle.Body.Door.Axle.02.Right BS;FrontDoorLeft;Lock;leftLatch01;Vehicle.Body.Door.Axle.01.Left;TX;boolean;Vehicle.Body.Door.Axle.01.Left BS;FrontDoorRight;Lock;rightLatch01;Vehicle.Body.Door.Axle.01.Right;TX;boolean;Vehicle.Body.Door.Axle.01.Right -BS;RearDoorLeft;Lock;leftLatch02;Vehicle.Body.Door.Axle.02.Left;TX;boolean;Vehicle.Body.Door.Axle.02.Left -BS;RearDoorRight;Lock;rightLatch02;Vehicle.Body.Door.Axle.02.Right;TX;boolean;Vehicle.Body.Door.Axle.02.Right diff --git a/examples/door_demo_example/vss_doors_rear_example.csv b/examples/door_demo_example/vss_doors_rear_example.csv new file mode 100644 index 0000000..285c0e5 --- /dev/null +++ b/examples/door_demo_example/vss_doors_rear_example.csv @@ -0,0 +1,10 @@ +;Class name;Function name;Signal name;vss;Signal direction;type;DBC CAN name includes CAN message name +;;;;;;; +VD;RearDoorLeft;IsOpen;leftDoorIsOpen02;Vehicle.Body.Door.Axle.02.Left;RX;boolean;CAN_Input_L2.Door02LeftIsOpen +VD;RearDoorRight;IsOpen;rightDoorIsOpen02;Vehicle.Body.Door.Axle.02.Right;RX;boolean;CAN_Input_R2.Door02RightIsOpen +VD;RearDoorLeft;Lock;leftLatch02;Vehicle.Body.Door.Axle.02.Left;TX;boolean;CAN_Output.LockDoor02Left +VD;RearDoorRight;Lock;rightLatch02;Vehicle.Body.Door.Axle.02.Right;TX;boolean;CAN_Output.LockDoor02Right +BS;RearDoorLeft;IsOpenL2;leftDoorIsOpen02;Vehicle.Body.Door.Axle.02.Left;RX;boolean;Vehicle.Body.Door.Axle.02.Left +BS;RearDoorRight;IsOpenR2;rightDoorIsOpen02;Vehicle.Body.Door.Axle.02.Right;RX;boolean;Vehicle.Body.Door.Axle.02.Right +BS;RearDoorLeft;Lock;leftLatch02;Vehicle.Body.Door.Axle.02.Left;TX;boolean;Vehicle.Body.Door.Axle.02.Left +BS;RearDoorRight;Lock;rightLatch02;Vehicle.Body.Door.Axle.02.Right;TX;boolean;Vehicle.Body.Door.Axle.02.Right diff --git a/examples/open_trunk_example/open_trunk_app/console.cpp b/examples/open_trunk_example/open_trunk_app/console.cpp index 1975242..612318b 100644 --- a/examples/open_trunk_example/open_trunk_app/console.cpp +++ b/examples/open_trunk_example/open_trunk_app/console.cpp @@ -34,9 +34,11 @@ const CConsole::SConsolePos g_sComment3{ 24, 1 }; const CConsole::SConsolePos g_sSeparator5{ 26, 1 }; const CConsole::SConsolePos g_sOpenViaDevice{ 28, 1 }; const CConsole::SConsolePos g_sOpenViaService{ 29, 1 }; -const CConsole::SConsolePos g_sSeparator6{ 31, 1 }; -const CConsole::SConsolePos g_sControlDescription{ 33, 1 }; -const CConsole::SConsolePos g_sCursor{ 34, 1 }; +const CConsole::SConsolePos g_sAutoCloseTrunk{ 30, 1 }; +const CConsole::SConsolePos g_sStatusTrunk{ 32, 1 }; +const CConsole::SConsolePos g_sSeparator6{ 34, 1 }; +const CConsole::SConsolePos g_sControlDescription{ 36, 1 }; +const CConsole::SConsolePos g_sCursor{ 37, 1 }; CConsole::CConsole() { @@ -92,6 +94,7 @@ void CConsole::PrintHeader(uint32_t uiInstance) { subTtitle = "Connected to core instance "; subTtitle.append(std::to_string(uiInstance)); + subTtitle.append(" (not all interfaces available)"); } // Print the titles PrintText(g_sTitle, "Open Trunk example: Open trunk when vehicle is not moving"); @@ -108,7 +111,7 @@ void CConsole::PrintHeader(uint32_t uiInstance) PrintText(g_sSeparator4, "============================================================================"); PrintText(g_sComment1, "The Open Trunk signal has a safety request."); PrintText(g_sComment2, "Therefore the speed value is required and must be checked."); - PrintText(g_sComment3, "If the car is moving, open is blocked and trunk is closed automatically."); + PrintText(g_sComment3, "If the vehicle is moving, open is blocked and trunk is closed automatically."); PrintText(g_sSeparator5, "============================================================================"); PrintText(g_sOpenViaDevice, "Vehicle Device Interface not available."); PrintText(g_sOpenViaService, "Basic Service Interface not available."); @@ -124,7 +127,7 @@ bool CConsole::PrepareDataConsumers() if (m_SignalSpeed) PrintValue(g_sDataLinkSpeed, "Vehicle Speed RX", m_SpeedDL, "m/s"); - // Registrate for the vehicle device & basic service of the speed. + // Register for the vehicle device & basic service of the speed. auto vehicleDevice = sdv::core::GetObject("Vehicle.Speed_Device").GetInterface(); if (vehicleDevice) { @@ -153,6 +156,10 @@ bool CConsole::PrepareDataConsumers() else PrintText(g_sOpenViaDevice, "Trunk Device NOT available"); + m_SignalStatusTrunk = dispatch.RegisterTxSignal(trunk::dsTrunk, 0); + if (!m_SignalStatusTrunk) + PrintText(g_sStatusTrunk, "Trunk Status not available."); + return true; } @@ -172,6 +179,9 @@ void CConsole::ResetSignals() // Unregister the data link signals if (m_SignalSpeed) m_SignalSpeed.Reset(); + + if (m_SignalStatusTrunk) + m_SignalStatusTrunk.Reset(); } void CConsole::CallbackSpeed(sdv::any_t value) @@ -246,7 +256,13 @@ void CConsole::SetSpeed(float value) if (bIsMoving && !m_bWasMovingBS) { - PrintText(g_sOpenViaService, "Safety active: speed > 0, trunk close command sent."); + if (m_pTrunkService) + { + if (m_pTrunkService->SetOpen(false)) + PrintText(g_sAutoCloseTrunk, "Safety feature active: Trunk closed as the vehicle started moving."); + else + PrintText(g_sOpenViaService, "Close trunk automatically failed."); + } } m_bWasMovingBS = bIsMoving; } @@ -278,9 +294,24 @@ char CConsole::GetChar() void CConsole::RunUntilBreak() { bool bRunning = true; + bool bTrunkClosed = true; while (bRunning) { + if(m_SignalStatusTrunk) + { + auto value =m_SignalStatusTrunk.Read().get(); + if (bTrunkClosed != value) + { + bTrunkClosed = value; + if (bTrunkClosed) + PrintText(g_sStatusTrunk, "Status: Trunk is open."); + else + PrintText(g_sStatusTrunk, "Status: Trunk is closed."); + } + } + + // Check for a key if (!KeyHit()) { @@ -294,17 +325,19 @@ void CConsole::RunUntilBreak() { case 'c': case 'C': - PrintText(g_sOpenViaDevice, " "); - PrintText(g_sOpenViaService, " "); + PrintText(g_sOpenViaDevice, " "); + PrintText(g_sOpenViaService, " "); + PrintText(g_sAutoCloseTrunk, " "); break; case 'd': case 'D': if (m_pTrunkDevice) { + PrintText(g_sAutoCloseTrunk, " "); if (m_pTrunkDevice->WriteOpen(true)) { if (m_SpeedVD || m_SpeedBS) - PrintText(g_sOpenViaDevice, "Trunk opened directly although car was moving, not safe."); + PrintText(g_sOpenViaDevice, "Trunk opened directly although vehicle was moving, not safe."); else PrintText(g_sOpenViaDevice, "Trunk opened directly, not safe."); } @@ -316,10 +349,11 @@ void CConsole::RunUntilBreak() case 'O': if (m_pTrunkService) { + PrintText(g_sAutoCloseTrunk, " "); if (m_pTrunkService->SetOpen(true)) PrintText(g_sOpenViaService, "Trunk opened safely (auto-close if vehicle starts moving)."); else - PrintText(g_sOpenViaService, "Open trunk failed, car is moving."); + PrintText(g_sOpenViaService, "Open trunk failed, vehicle is moving."); } break; case 'x': diff --git a/examples/open_trunk_example/open_trunk_app/console.h b/examples/open_trunk_example/open_trunk_app/console.h index 326e833..0ce6338 100644 --- a/examples/open_trunk_example/open_trunk_app/console.h +++ b/examples/open_trunk_example/open_trunk_app/console.h @@ -157,6 +157,7 @@ private: mutable std::mutex m_mtxPrintToConsole; ///< Mutex to print complete message bool m_bRunning = false; ///< When set, the application is running. + sdv::core::CSignal m_SignalStatusTrunk; ///< Status of the trunk, only available in the standalone example sdv::core::CSignal m_SignalSpeed; ///< Speed float m_SpeedDL = 0.0; ///< Speed Data Link float m_SpeedVD = 0.0; ///< Speed Vehicle Device diff --git a/examples/system_demo_example/example_app/console.cpp b/examples/system_demo_example/example_app/console.cpp index f9b588e..114ec69 100644 --- a/examples/system_demo_example/example_app/console.cpp +++ b/examples/system_demo_example/example_app/console.cpp @@ -209,8 +209,7 @@ bool CConsole::PrepareDataConsumers() m_pCounterSteeringSvc = sdv::core::GetObject("Counter Steering Example Service").GetInterface(); if (!m_pCounterSteeringSvc) { - std::cerr << "Console ERROR: Could not get complex service interface 'ICounterSteeringService'" << std::endl; - return false; + PrintText(g_sCSActive, "Attention: Complex service 'CounterSteeringService' is not available."); } return true; diff --git a/examples/system_demo_example/example_app/control.cpp b/examples/system_demo_example/example_app/control.cpp index 5090b88..3eac499 100644 --- a/examples/system_demo_example/example_app/control.cpp +++ b/examples/system_demo_example/example_app/control.cpp @@ -103,7 +103,7 @@ void CExampleControl::StartTestRun() // Start the simulated datalink m_bRunning = true; - m_threadSimulateDatalink = std::thread(&CExampleControl::SimulateDatalinkThreadFunc, this); + m_threadSimulateDatalink = sdv::core::secure_thread(&CExampleControl::SimulateDatalinkThreadFunc, this); } void CExampleControl::StopTestRun() diff --git a/examples/system_demo_example/example_app/control.h b/examples/system_demo_example/example_app/control.h index c6d8e7e..5a343f6 100644 --- a/examples/system_demo_example/example_app/control.h +++ b/examples/system_demo_example/example_app/control.h @@ -163,20 +163,20 @@ private: verbose, ///< Extensive reporting by application control }; - bool m_bInitialized = false; ///< Set when initialized. - bool m_bCmdLnError = false; ///< Command line error occurred. - bool m_bCmdLnHelp = false; ///< Command line help provided. - std::atomic_bool m_bRunning = false; ///< When set, the application is running. - ERunAs m_eRunAs = ERunAs::standalone; ///< Application operation. - EAppControlReporting m_eReporting = EAppControlReporting::silent; ///< Application control reporting. - uint32_t m_uiInstance = 1000u; ///< Server instance to connect to. - std::filesystem::path m_pathFramework; ///< Path to the SDV V-API framework. - sdv::app::CAppControl m_appcontrol; ///< App-control of SDV V-API. - std::thread m_threadSimulateDatalink; ///< Simulation datalink thread. - sdv::core::CSignal m_signalSteeringWheel; ///< Steering wheel angle signal (input) - simulated datalink - sdv::core::CSignal m_signalSpeed; ///< Speed signal (input) - simulated datalink - sdv::core::CSignal m_signalRearAngle; ///< Rear angle signal (output) - simulated datalink - sdv::core::CSignal m_signalCounter; ///< Simple counter signal (output) - simulated datalink + bool m_bInitialized = false; ///< Set when initialized. + bool m_bCmdLnError = false; ///< Command line error occurred. + bool m_bCmdLnHelp = false; ///< Command line help provided. + std::atomic_bool m_bRunning = false; ///< When set, the application is running. + ERunAs m_eRunAs = ERunAs::standalone; ///< Application operation. + EAppControlReporting m_eReporting = EAppControlReporting::silent; ///< Application control reporting. + uint32_t m_uiInstance = 1000u; ///< Server instance to connect to. + std::filesystem::path m_pathFramework; ///< Path to the SDV V-API framework. + sdv::app::CAppControl m_appcontrol; ///< App-control of SDV V-API. + sdv::core::secure_thread m_threadSimulateDatalink; ///< Simulation datalink thread. + sdv::core::CSignal m_signalSteeringWheel; ///< Steering wheel angle signal (input) - simulated datalink + sdv::core::CSignal m_signalSpeed; ///< Speed signal (input) - simulated datalink + sdv::core::CSignal m_signalRearAngle; ///< Rear angle signal (output) - simulated datalink + sdv::core::CSignal m_signalCounter; ///< Simple counter signal (output) - simulated datalink }; template diff --git a/examples/system_demo_example/fmu/model.cpp b/examples/system_demo_example/fmu/model.cpp index 3983302..c75a484 100644 --- a/examples/system_demo_example/fmu/model.cpp +++ b/examples/system_demo_example/fmu/model.cpp @@ -103,7 +103,10 @@ bool CreateCoreServiceTomlFile(const std::string& resources) std::ofstream tomlFile("sdv_core_reloc.toml"); if (tomlFile.is_open()) { - tomlFile << "# Location of the SDV binaries and configuration files\ndirectory = \""; + tomlFile << R"toml(# Location of the SDV binaries and configuration files +[CoreLocation] +Version = 100 +Framework = ")toml"; tomlFile << resources; tomlFile << "\"\n"; tomlFile.close(); diff --git a/export/interfaces/app.idl b/export/interfaces/app.idl index 4efcaff..5357769 100644 --- a/export/interfaces/app.idl +++ b/export/interfaces/app.idl @@ -103,12 +103,6 @@ module sdv * @return The instance ID. */ uint32 GetInstanceID() const; - - /** - * @brief Return the number of retries to establish a connection. - * @return Number of retries. - */ - uint32 GetRetries() const; }; /** @@ -181,5 +175,96 @@ module sdv void RequestShutdown() raises(XAccessDenied, XInvalidState); }; + /** + * @brief Load and save the application settings. + * @remarks The interface is only available in maintenance mode. + */ + interface IAppSettingsPersist + { + /** + * @brief Load the application settings. + * @remarks When there is no settings file, this is not an error. Default settings will be assumed. + * @return Returns whether the loading was successful. + */ + boolean LoadSettings(); + + /** + * @brief Save the application settings file (or create when not existing yet). + * @return Returns whether the saving was successful. + */ + boolean SaveSettings(); + }; + + /** + * @brief Application connections + * @remarks The interface is only available in maintenance mode. + */ + interface IAppConnections + { + /** + * @brief Get a sequence with listener names. + * @return Sequence with listener name strings. + */ + sequence GetListeners() const; + + /** + * @brief Get the listener configuration. + * @param[in] ssName Name of the listener. + * @return String containing the listener configuration. + */ + u8string GetListenerConfig(in u8string ssName) const; + + /** + * @brief Add or update a listener configuration. + * @param[in] ssName Name of the listener configuration. + * @param[in] ssConfig The configuration string for the listener. + * @return Returns whether the listener could be added. + */ + boolean AddListenerConfig(in u8string ssName, in u8string ssConfig); + + /** + * @brief Remove a listener configuration with the provided name. + * @param[in] ssName Name of the listener configuration. + * @return Returns whether the removal was successful. + */ + boolean RemoveListenerConfig(in u8string ssName); + + /** + * @brief Get a sequence with connection names. + * @return Sequence with name strings. + */ + sequence GetConnections() const; + + /** + * @brief Get the connection configuration. + * @param[in] ssName Name of the connection. + * @return String containing the connection configuration. + */ + u8string GetConnectionConfig(in u8string ssName) const; + + /** + * @brief Add or update a connection configuration. + * @param[in] ssName Name of the connection configuration. + * @param[in] ssConfig The configuration string for the connection. + * @param[in] ssInsertBefore Reference to the string to connection to insert the the new connection before, or empty + * when the the new connection should be placed at the end. + * @return Returns whether the connection could be added (fails when the connection already exists). + */ + boolean AddConnectionConfig(in u8string ssName, in u8string ssConfig, in u8string ssInsertBefore); + + /** + * @brief Remove a connection configuration with the provided name. + * @param[in] ssName Name of the connection configuration. + * @return Returns whether the removal was successful. + */ + boolean RemoveConnectionConfig(in u8string ssName); + + /** + * @brief Return the number of retries to establish a connection. + * @return Number of retries. + */ + uint32 GetConnectRetries() const; + }; + }; // module app }; // module sdv diff --git a/export/interfaces/com.idl b/export/interfaces/com.idl index 2f2d392..e82fcd8 100644 --- a/export/interfaces/com.idl +++ b/export/interfaces/com.idl @@ -53,31 +53,56 @@ module sdv }; /** - * @brief Connect to a remote system. Provided by the ConnectClient utility. + * @brief Connect to a remote system. Provided by the Connection service. + * @details This interface is exposed by the connection client service. The configuration is provided during instantiation + * of the object and is of the form: + * @code + * # Provider to use for the connection + * [Provider] + * Name = "" + * + * # Additional channel information for the client + * [IpcChannel] + * xyz = "" + * @endcode + * + * For example for shared memory: + * @code + * [Provider] + * Name = "DefaultSharedMemory" + * [IpcChannel] + * Name = "LISTENER_1234" + * @endcode */ interface IClientConnect { /** - * @brief Connect to a remote system using the connection string to contact the system. - * @remarks After a successful connection, the ConnectClient utility is not needed any more. - * @param[in] ssConnectString Optional connection string to use for connection. If not provided, the connection will - * automatically get the connection ID from the app-control service (default). The connection string for a local - * connection can be of the form: - * @code - * [Client] - * Type = "local" - * Instance = 1234 # Optional: only use when connecting to a system with a different instance ID. - * @endcode - * And the following can be used for a remote connection: - * @code - * [Client] - * Type = "remote" - * Interface = "127.0.0.1" - * Port = 2000 - * @endcode - * @return Returns an interface to the repository of the remote system or a NULL pointer if not found. + * @brief Connect to a remote system. + * @return Returns whether connect was successful. */ - IInterfaceAccess Connect(in u8string ssConnectString) raises(XAccessDenied, XNotFound, XInvalidState, XTimeout); + boolean Connect() raises(XAccessDenied, XNotFound, XInvalidState, XTimeout); + + /** + * @brief Disconnect from a connected system. + * @return Returns whether disconnect was successful. + */ + boolean Disconnect() raises(XAccessDenied, XNotFound, XInvalidState, XTimeout); + + /** + * @brief State of the current connection. + * @return Returns whether an active connection exists. + */ + boolean IsConnected() const; + + /** + * @brief Get the remote repository that is available after connection. + * @remarks For main, isolated and external applications, the remote repository will be automatically linked to the + * local repository. Hence a requests for the repository is not needed. For all other applications, access must be + * explicitly acquired through this interface. + * @return Interface to the remote repository if a successful connection is established. The interface is valid until + * disconnect is called or the client connection service is terminated. + */ + IInterfaceAccess GetRemoteRepository(); }; /** diff --git a/export/interfaces/config.idl b/export/interfaces/config.idl index a598df2..6c44b9f 100644 --- a/export/interfaces/config.idl +++ b/export/interfaces/config.idl @@ -389,6 +389,17 @@ module sdv u8string ssInstallName; ///< Name of the installation. }; + /** + * @brief The package exceeds the maximum size. + */ + exception XPackageSizeExceeded : XSysExcept + { + /** Description */ + const char _description[] = "Maximum package size exceeded."; + + u8string ssFileName; ///< Name of the file which causes the exception. + }; + /** * @brief The installation doesn't contain a module. */ diff --git a/export/interfaces/core.idl b/export/interfaces/core.idl index 3685736..d537a35 100644 --- a/export/interfaces/core.idl +++ b/export/interfaces/core.idl @@ -57,11 +57,28 @@ module sdv template TInterface* GetInterface() { - return GetInterface(sdv::GetInterfaceId()).template get(); + try + { + return GetInterface(sdv::GetInterfaceId()).template get(); + } catch (const XSysExcept&) + { + return nullptr; + } } #verbatim_end }; + /** + * @brief Core features. + */ + module core + { + /** + * @brief Object ID. + */ + typedef uint64 TObjectID; + }; + /** * @brief Object type enumeration. */ @@ -163,13 +180,24 @@ module sdv /** * @brief Component operation mode. - */ + */ enum EOperationMode : uint32 { configuring = 20, ///< The component should switch to configuration mode. running = 30, ///< The component should switch to running mode. }; + /** + * @brief Object information supplied to the object initialization function. + */ + struct SObjectInfo + { + core::TObjectID tObjectID; ///< Object ID (local for the running process) + SClassInfo sClassInfo; ///< Object class information + u8string ssName; ///< Object name + u8string ssConfig; ///< Object configuration TOML + }; + /** * @brief Optional interface allowing for additional control of more complex SDVObjects. * To be implemented if the object in question calls other SDVobjects via running threads or callbacks, @@ -179,9 +207,9 @@ module sdv { /** * @brief Initialize the object. - * @param[in] ssObjectConfig Optional configuration string. + * @param[in] sObjectInfo The registration information of this object. */ - void Initialize(in u8string ssObjectConfig); + void Initialize(in SObjectInfo sObjectInfo); /** * @brief Get the current state of the object. @@ -247,50 +275,6 @@ module sdv */ uint32 GetCount() const; }; - - /** - * @brief Attribute flags. - */ - enum EAttributeFlags : uint32 - { - read_only = 0x100, ///< When set, the attribute is readonly. - }; - - /** - * @brief Attribute interface - */ - interface IAttributes - { - /** - * @brief Get a sequence with the available attribute names. - * @return The sequence of attribute names. - */ - sequence GetNames() const; - - /** - * @brief Get the attribute value. - * @param[in] ssAttribute Name of the attribute. - * @return The attribute value or an empty any-value if the attribute wasn't found or didn't have a value. - */ - any Get(in u8string ssAttribute) const; - - /** - * @brief Set the attribute value. - * @param[in] ssAttribute Name of the attribute. - * @param[in] anyAttribute Attribute value to set. - * @return Returns 'true' when setting the attribute was successful or 'false' when the attribute was not found or the - * attribute is read-only or another error occurred. - */ - boolean Set(in u8string ssAttribute, in any anyAttribute); - - /** - * @brief Get the attribute flags belonging to a certain attribute. - * @param[in] ssAttribute Name of the attribute. - * @return Returns the attribute flags (zero or more EAttributeFlags flags) or 0 when the attribute could not be found. - */ - uint32 GetFlags(in u8string ssAttribute) const; - }; - }; // module sdv #verbatim_begin diff --git a/export/interfaces/core_ps.idl b/export/interfaces/core_ps.idl index 04d4df3..c72a873 100644 --- a/export/interfaces/core_ps.idl +++ b/export/interfaces/core_ps.idl @@ -13,6 +13,7 @@ #include "core.idl" #include "process.idl" +#include "permission.idl" /** * @brief Software Defined Vehicle framework. @@ -149,6 +150,7 @@ module sdv TMarshallID tProxyID; ///< Proxy id to identify the proxy this packet is sending from or destined to. TMarshallID tStubID; ///< Stub id to identify the stub this packet is destined to or receiving from. uint64 uiCallIndex; ///< Call index to uniquely identify the call. + core::TPermissionID tPermissionID; ///< Permission ID used to elevate permission, or 0 for standard permission. }; /** diff --git a/export/interfaces/ipc.idl b/export/interfaces/ipc.idl index 75cd4a6..ab623c0 100644 --- a/export/interfaces/ipc.idl +++ b/export/interfaces/ipc.idl @@ -35,12 +35,15 @@ module sdv ///< identification must be similar to the following example: ///< @code ///< [Provider] - ///< Name = "DefaultSharedMemoryChannelControl" + ///< Name = "DefaultSharedMemory" ///< @endcode }; /** - * @brief Interface for creating an IPC connection, which other participants can connect to via IChannelAccess + * @brief Interface for creating an IPC connection, which other participants can connect to via IChannelAccess. + * @attention The channel control object should allow the creation of channels with a fixed configuration (e.g. a provided + * name or a port), which is necessary for the creation of a listener. It also should allow dynamic channel creation (e.g. + * automatic name or port-number) to create additional connections between components. */ interface ICreateEndpoint { diff --git a/export/interfaces/permission.idl b/export/interfaces/permission.idl new file mode 100644 index 0000000..f1a3fa3 --- /dev/null +++ b/export/interfaces/permission.idl @@ -0,0 +1,96 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#include "core.idl" + +/** + * @brief Software Defined Vehicle framework. + */ +module sdv +{ + /** + * @brief Core features. + */ + module core + { + /// ID corresponding to a thread specific permission request. + typedef uint64 TPermissionID; + + /// ID identifying a permission transfer. + typedef uint64 TPermissionTransferID; + + /** + * @brief Access type restriction. The lower the access, the more restricted. + */ + enum EAccessPermission : int32 + { + not_set = -1001, ///< Permissions are not set; will be treated as restricted access. + restricted_access = -1000, ///< Restricted access (default when no other set). + remote_access = -200, ///< Access for remote execution. Access allowed to basic and complex services. + local_access = -100, ///< Access for isolated execution. Access allowed to system, basic and complex services. + full_access = 0, ///< Full access is allowed to complete system. + }; + + /** + * @brief Permission control interface. This interface is used for restricting permission on a thread and transferring + * permissions from one thread to another. + */ + interface IPermissionControl + { + /** + * @brief Restrict the access permission for the current thread. + * @remarks The access restriction will be assigned to the current thread and combined with previous and future + * permissions. The lowest assigned permission will determine the actual access permission for the current thread. + * @remarks The access restriction will stay in effect until it is released by the function ReleaseAccessPermission. + * @remarks A newly created thread has fully restricted access. This cannot be changed using this function. Use an + * access restriction transfer from one thread to this thread to set a higher level of access permission. + * @param[in] ePermission The permission to restrict to. + * @return The permission ID for this restriction or 0 when the access permission could not be set. Use the + * ReleaseAccessPermission to release the restriction again. + */ + TPermissionID RestrictAccessPermission(in EAccessPermission ePermission); + + /** + * @brief Release a previously set access restriction for the current thread. + * @param[in] tPermissionID The ID of the access restriction previously set for the current thread. + * @return Returns whether the restriction could be released successfully. + */ + boolean ReleaseAccessPermission(in TPermissionID tPermissionID); + + /** + * @brief Prepares a transfer of the access restriction from the current thread. + * @return The ID of the transfer object containing the current access permissions or 0 when the transfer preparation + * has failed. + */ + TPermissionTransferID TransferCurrentPermission(); + + /** + * @brief Set the access permission using a transfer object from one thread to another. + * @remarks An new thread has fully restricted access per default. Use this function to set the required access level. + * If a thread has already initialized with the proper access level, this function will set identical or lower access + * permissions for the current thread. + * @param[in] tTransferID The IS of the prepared access permission transfer. + * @return The permission ID for this restriction or 0 when the access permission could not be transferred. Use the + * ReleaseAccessPermission to release the restriction again. + */ + TPermissionID SetAccessPermission(in TPermissionTransferID tTransferID); + + /** + * @brief Get the access permission level for the current thread. This will be the lowest restriction set for the + * current thread. + * @return The current access permission level. + */ + EAccessPermission GetCurrentPermission() const; + }; + }; // module core +}; // module sdv diff --git a/export/interfaces/repository.idl b/export/interfaces/repository.idl index 6965a02..527293a 100644 --- a/export/interfaces/repository.idl +++ b/export/interfaces/repository.idl @@ -25,11 +25,6 @@ module sdv module core { - /** - * @brief Object ID. - */ - typedef uint64 TObjectID; - /** * @brief Interface used to access objects from other modules via the repository service */ @@ -229,6 +224,32 @@ module sdv TObjectID RegisterObject(in IInterfaceAccess pObjectIfc, in u8string ssObjectName); }; + /** + * @brief The object dependency can be used to state dependencies between two objects. This influence is used during + * shutdown to determine the termination order. + */ + interface IObjectDependency + { + /** + * @brief Add a dependency for an object. + * @param ssObjectName Name of the object. + * @param ssDependsOnObject Name of the object it depends on. + */ + void AddObjectDependency(in u8string ssObjectName, in u8string ssDependsOnObject); + + /** + * @brief Remove an object dependency. + * @param ssObjectName Name of the object. + * @param ssDependsOnObject Name of the object it depends on. + */ + void RemoveObjectDependency(in u8string ssObjectName, in u8string ssDependsOnObject); + }; + + /** + * @brief Link ID. + */ + typedef uint64 TLinkID; + /** * @brief Connect the core repository to the local repository to allow object access by the locally running components. * @remarks This interface is available only for isolated and external applications. @@ -238,13 +259,15 @@ module sdv /** * @brief Register the core repository. * @param[in] pCoreRepository Pointer to the proxy interface of the core repository. + * @return Returns a link ID to be used in the Unlink function. Or 0 on failure. */ - void LinkCoreRepository(in IInterfaceAccess pCoreRepository); + TLinkID LinkCoreRepository(in IInterfaceAccess pCoreRepository); /** * @brief Unlink a previously linked core repository. + * @param[in] tLinkID Link ID of the repository link to remove. */ - void UnlinkCoreRepository(); + void UnlinkCoreRepository(in TLinkID tLinkID); }; }; // module core diff --git a/export/interfaces/toml.idl b/export/interfaces/toml.idl index 0f94af6..98d6852 100644 --- a/export/interfaces/toml.idl +++ b/export/interfaces/toml.idl @@ -142,10 +142,10 @@ module sdv u8string GetComment(in ECommentType eType); /** - * @brief Format the node automatically. This will remove the whitespace between the elements within the node. Comments - * will not be changed. + * @brief Format the node automatically, remove redundant whitespace. + * @param[in] bRemoveComments When set, the comments are removed from the node. */ - void AutomaticFormat(); + void AutomaticFormat(in boolean bRemoveComments); /** * @brief Is the node inline? @@ -265,10 +265,21 @@ module sdv /** * @brief Convert the node to a standard node. + * @param[in] bIncludeChildren When set, applicable child nodes are made are converted to standard nodes as well (only + * tables and table-arrays can be defined as standard). * @return Returns whether the conversion was successful. Returns 'true' when the node was already defined as standard * node. */ - boolean MakeStandard(); + boolean MakeStandard(in boolean bIncludeChildren); + }; + + /** + * @brief Insertion preference for tables and table arrays, being standard or inline. + */ + enum EInsertPreference + { + prefer_standard = 0, ///< When the parent node is not inline, the node will be inserted as standard node. + prefer_inline = 1, ///< The node will be inserted as inline node. }; /** @@ -278,71 +289,67 @@ module sdv { /** * @brief Insert a value into the collection at the location before the supplied index. - * @param[in] uiIndex The insertion location to insert the node before. Can be npos or any value larger than the - * collection count to insert the node at the end of the collection. Value nodes cannot be inserted behind external - * tables and table arrays. If the index is referencing a position behind an external table or a table array, the index - * is automatically corrected. - * @param[in] ssName Name of the node to insert. Will be ignored for an array collection. The name must adhere to the - * key names defined by the TOML specification. Defining the key multiple times is not allowed. Quotation of key names - * is done automatically; the parser decides itself whether the key is bare-key, a literal key or a quoted key. + * @remarks In TOML, inline nodes are located before standard nodes. Since values are presented as inline node, they + * will be inserted before any standard node (table or table array if defined as standard node). + * @param[in] ssInsertBefore Name of the node to insert the value before. In case of an array, can be an index between + * square brackets. Can be empty, causing the node to be inserted at the end. + * @param[in] ssName Name of the node to insert. This name can contain parent nodes, which are automatically created if + * not existing. With arrays, since the value in TOML doesn't have a name, the name of the value must be an empty + * string and any names provided are considered parent nodes. The name must adhere to the key names defined by the TOML + * specification. Defining the key multiple times is not allowed. Quotation of key names is done automatically; the + * parser decides itself whether the key is bare-key, a literal key or a quoted key. * @param[in] anyValue The value of the node, being either an integer, floating point number, boolean value or a string. * Conversion is automatically done to int64, double float, bool or u8string. * @return On success the interface to the newly inserted node is returned or NULL otherwise. */ - IInterfaceAccess InsertValue(in uint32 uiIndex, in u8string ssName, in any anyValue); + IInterfaceAccess InsertValue(in u8string ssInsertBefore, in u8string ssName, in any anyValue); /** * @brief Insert an array into the collection at the location before the supplied index. - * @param[in] uiIndex The insertion location to insert the node before. Can be npos or any value larger than the - * collection count to insert the node at the end of the collection. Array nodes cannot be inserted behind external - * tables and table arrays. If the index is referencing a position behind an external table or a table array, the index - * is automatically corrected. - * @param[in] ssName Name of the array node to insert. Will be ignored if the current node is also an array collection. - * The name must adhere to the key names defined by the TOML specification. Defining the key multiple times is not - * allowed. Quotation of key names is done automatically; the parser decides itself whether the key is bare-key, a - * literal key or a quoted key. + * @remarks In TOML, inline nodes are located before standard nodes. Since arrays are presented as inline node, they + * will be inserted before any standard node (table or table array if defined as standard node). + * @param[in] ssInsertBefore Name of the node to insert the value before. In case of an array, can be an index between + * square brackets. Can be empty, causing the node to be inserted at the end. + * @param[in] ssName Name of the node to insert. This name can contain parent nodes, which are automatically created if + * not existing. With arrays, since the value in TOML doesn't have a name, the name of the value must be an empty + * string and any names provided are considered parent nodes. The name must adhere to the key names defined by the TOML + * specification. Defining the key multiple times is not allowed. Quotation of key names is done automatically; the + * parser decides itself whether the key is bare-key, a literal key or a quoted key. * @return On success the interface to the newly inserted node is returned or NULL otherwise. */ - IInterfaceAccess InsertArray(in uint32 uiIndex, in u8string ssName); - - /** - * @brief Insertion preference for tables and table arrays, being standard or inline. - */ - enum EInsertPreference - { - prefer_standard = 0, ///< When the parent node is not inline, the node will be inserted as standard node. - prefer_inline = 1, ///< The node will be inserted as inline node. - }; + IInterfaceAccess InsertArray(in u8string ssInsertBefore, in u8string ssName); /** * @brief Insert a table into the collection at the location before the supplied index. - * @param[in] uiIndex The insertion location to insert the node before. Can be npos or any value larger than the - * collection count to insert the node at the end of the collection. Table nodes cannot be inserted before value nodes - * or arrays. If the index is referencing a position before a value node or an array, the index is automatically - * corrected. - * @param[in] ssName Name of the table node to insert. Will be ignored if the parent node is an array collection. - * The name must adhere to the key names defined by the TOML specification. Defining the key multiple times is not - * allowed. Quotation of key names is done automatically; the parser decides itself whether the key is bare-key, a - * literal key or a quoted key. + * @remarks In TOML, inline nodes are located before standard nodes. Tables can be inserted as inline node, in which + * case they will be inserted before any standard node (table or table array if defined as standard node). + * @param[in] ssInsertBefore Name of the node to insert the value before. In case of an array, can be an index between + * square brackets. Can be empty, causing the node to be inserted at the end. + * @param[in] ssName Name of the node to insert. This name can contain parent nodes, which are automatically created if + * not existing. With arrays, since the value in TOML doesn't have a name, the name of the value must be an empty + * string and any names provided are considered parent nodes. The name must adhere to the key names defined by the TOML + * specification. Defining the key multiple times is not allowed. Quotation of key names is done automatically; the + * parser decides itself whether the key is bare-key, a literal key or a quoted key. * @param[in] ePreference The preferred form of the node to be inserted. * @return On success the interface to the newly inserted node is returned or NULL otherwise. */ - IInterfaceAccess InsertTable(in uint32 uiIndex, in u8string ssName, in EInsertPreference ePreference); + IInterfaceAccess InsertTable(in u8string ssInsertBefore, in u8string ssName, in EInsertPreference ePreference); /** * @brief Insert a table array into the collection at the location before the supplied index. - * @param[in] uiIndex The insertion location to insert the node before. Can be npos or any value larger than the - * collection count to insert the node at the end of the collection. Table array nodes cannot be inserted before value - * nodes or arrays. If the index is referencing a position before a value node or an array, the index is automatically - * corrected. - * @param[in] ssName Name of the array node to insert. Will be ignored if the parent node is also an array collection. - * The name must adhere to the key names defined by the TOML specification. Defining the key multiple times is not - * allowed. Quotation of key names is done automatically; the parser decides itself whether the key is bare-key, a - * literal key or a quoted key. + * @remarks In TOML, inline nodes are located before standard nodes. Table arrays can be inserted as inline node, in + * which case they will be inserted before any standard node (table or table array if defined as standard node). + * @param[in] ssInsertBefore Name of the node to insert the value before. In case of an array, can be an index between + * square brackets. Can be empty, causing the node to be inserted at the end. + * @param[in] ssName Name of the node to insert. This name can contain parent nodes, which are automatically created if + * not existing. With arrays, since the value in TOML doesn't have a name, the name of the value must be an empty + * string and any names provided are considered parent nodes. The name must adhere to the key names defined by the TOML + * specification. Defining the key multiple times is not allowed. Quotation of key names is done automatically; the + * parser decides itself whether the key is bare-key, a literal key or a quoted key. * @param[in] ePreference The preferred form of the node to be inserted. * @return On success the interface to the newly inserted node is returned or NULL otherwise. */ - IInterfaceAccess InsertTableArray(in uint32 uiIndex, in u8string ssName, in EInsertPreference ePreference); + IInterfaceAccess InsertTableArray(in u8string ssInsertBefore, in u8string ssName, in EInsertPreference ePreference); /** * @brief The result of the TOML string to insert. @@ -358,17 +365,15 @@ module sdv * @brief Insert a TOML string as a child of the current collection node. If the collection is a table, the TOML string * should contain values and inline/external/array-table nodes with names. If the collection is an array, the TOML * string should contain and inline table nodes without names. - * @attention Even though the TOML might be defining the node(s) in standard form, if the parent node is an inline node, - * the node will be inserted as inline node. - * @param[in] uiIndex The insertion location to insert the node before. Can be npos or any value larger than the - * collection count to insert the node at the end of the collection. Table array nodes cannot be inserted before value - * nodes or arrays. If the index is referencing a position before a value node or an array, the index is automatically - * corrected. + * @remarks In TOML, inline nodes are located before standard nodes. Dependable on the nodes defined in the TOML they + * might be transferred to inline or they might be inserted at a different location. + * @param[in] ssInsertBefore Name of the node to insert the value before. In case of an array, can be an index between + * square brackets. Can be empty, causing the node to be inserted at the end. * @param[in] ssTOML The TOML string to insert. This string can hold one or more nodes that should be inserted. * @param[in] bRollbackOnFailure If only part of the nodes could be inserted, no node will be inserted. * @return The result of the insertion. */ - EInsertResult InsertTOML(in uint32 uiIndex, in u8string ssTOML, in boolean bRollbackOnFailure); + EInsertResult InsertTOML(in u8string ssInsertBefore, in u8string ssTOML, in boolean bRollbackOnFailure); }; /** diff --git a/export/support/any.inl b/export/support/any.inl index 0442a91..23f0cdc 100644 --- a/export/support/any.inl +++ b/export/support/any.inl @@ -24,6 +24,9 @@ #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 26495) +#else + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" #endif namespace sdv @@ -1738,6 +1741,8 @@ namespace sdv #ifdef _MSC_VER #pragma warning(pop) +#else + #pragma GCC diagnostic pop #endif #endif // !defined SDV_ANY_INL \ No newline at end of file diff --git a/export/support/app_control.h b/export/support/app_control.h index 77135ed..cdffaa0 100644 --- a/export/support/app_control.h +++ b/export/support/app_control.h @@ -65,6 +65,7 @@ namespace sdv bool Startup(const std::string& rssConfig) { IAppControl* pAppControl = core::GetCore() ? core::GetCore() : nullptr; + if (!pAppControl) return false; if (m_eState != EAppOperationState::not_started) return false; try @@ -78,45 +79,6 @@ namespace sdv { m_eContext = pAppContext->GetContextType(); m_uiInstanceID = pAppContext->GetInstanceID(); - m_uiRetries = pAppContext->GetRetries(); - } - - // Automatically connect to the server - if (m_eContext == EAppContext::external) - { - // Try to connect - m_ptrServerRepository = sdv::com::ConnectToLocalServerRepository(m_uiInstanceID, m_uiRetries); - if (!m_ptrServerRepository) - { - if (!ConsoleIsSilent()) - std::cerr << "ERROR: Failed to connect to the server repository." << std::endl; - Shutdown(); - return false; - } - - // Get access to the module control service - sdv::core::IObjectAccess* pObjectAccess = m_ptrServerRepository.GetInterface(); - const sdv::core::IRepositoryControl* pRepoControl = nullptr; - if (pObjectAccess) - pRepoControl = sdv::TInterfaceAccessPtr(pObjectAccess->GetObject("RepositoryService")). - GetInterface(); - if (!pRepoControl) - { - if (!ConsoleIsSilent()) - std::cerr << "ERROR: Failed to access the server repository." << std::endl; - return false; - } - - // Link the local repository to the server repository. - sdv::core::ILinkCoreRepository* pLinkCoreRepo = - sdv::core::GetObject("RepositoryService"); - if (!pLinkCoreRepo) - { - if (!ConsoleIsSilent()) - std::cerr << "ERROR: Cannot link local and server repositories." << std::endl; - return false; - } - pLinkCoreRepo->LinkCoreRepository(m_ptrServerRepository); } return bRet; @@ -154,23 +116,6 @@ namespace sdv */ void Shutdown() { - // Disconnect local and remote repositories. - if (m_ptrServerRepository) - { - // Link the local repository to the server repository. - sdv::core::ILinkCoreRepository* pLinkCoreRepo = - sdv::core::GetObject("RepositoryService"); - if (!pLinkCoreRepo) - { - if (!ConsoleIsSilent()) - std::cerr << "ERROR: Cannot unlink local and server repositories." << std::endl; - } else - pLinkCoreRepo->UnlinkCoreRepository(); - } - - // Disconnect from the server (if connected at all). - m_ptrServerRepository.Clear(); - // Shutdown. IAppControl* pAppControl = core::GetCore() ? core::GetCore() : nullptr; try @@ -194,28 +139,86 @@ namespace sdv return m_eState == EAppOperationState::running; } + /** + * @brief Get the absolute path to the currently run executable + * @return Absolute std::filesystem::path to the currently run executable + */ + static std::filesystem::path GetAppDirectory() + { + static std::filesystem::path pathExeDir; + if (!pathExeDir.empty()) + return pathExeDir; +#ifdef _WIN32 + // Windows specific + std::wstring ssPath(32768, '\0'); + GetModuleFileNameW(NULL, ssPath.data(), static_cast(ssPath.size() - 1)); +#elif defined __linux__ + // Linux specific + std::string ssPath(PATH_MAX + 1, '\0'); + const ssize_t nCount = readlink("/proc/self/exe", ssPath.data(), PATH_MAX); + if (nCount < 0 || nCount >= PATH_MAX) + return pathExeDir; // some error + ssPath.at(nCount) = '\0'; +#else + #error OS is not supported! +#endif + pathExeDir = std::filesystem::path{ssPath.c_str()}.parent_path() / ""; // To finish the folder path with (back)slash + return pathExeDir; + } + + /** + * @brief Get the filename of the currently run executable + * @return The filename to the currently run executable + */ + static std::filesystem::path GetAppFilename() + { + static std::filesystem::path pathExeFilename; + if (!pathExeFilename.empty()) + return pathExeFilename; +#ifdef _WIN32 + // Windows specific + std::wstring ssPath(32768, '\0'); + GetModuleFileNameW(NULL, ssPath.data(), static_cast(ssPath.size() - 1)); +#elif defined __linux__ + // Linux specific + std::string ssPath(PATH_MAX + 1, '\0'); + const ssize_t nCount = readlink("/proc/self/exe", ssPath.data(), PATH_MAX); + if (nCount < 0 || nCount >= PATH_MAX) + return pathExeFilename; // some error + ssPath.at(nCount) = '\0'; +#else + #error OS is not supported! +#endif + pathExeFilename = std::filesystem::path{ssPath.c_str()}.filename(); + return pathExeFilename; + } + + /** * @brief Get the SDV_FRAMEWORK_RUNTIME environment variable for this application. - * @return Path directing to the SDV V-API Framework directory if available or an empty path if not. + * @remarks If the environment variable is empty or if the variable contains a relative path, the path will be enhanced + * with the application path. + * @return Absolute path directing to the SDV Vehicle API Framework directory. */ static std::filesystem::path GetFrameworkRuntimeDirectory() { + std::filesystem::path path; #ifdef _WIN32 const wchar_t* szFrameworkDir = _wgetenv(L"SDV_FRAMEWORK_RUNTIME"); - if (!szFrameworkDir) return {}; - return szFrameworkDir; + if (szFrameworkDir) path = szFrameworkDir; #elif defined __unix__ const char* szFrameworkDir = getenv("SDV_FRAMEWORK_RUNTIME"); - if (!szFrameworkDir) return {}; - return szFrameworkDir; + if (szFrameworkDir) path = szFrameworkDir; #else #error The OS is not supported! #endif + if (path.empty() || path.is_relative()) path = GetAppDirectory() / path; + return path.lexically_normal(); } /** * @brief Set or overwrite the SDV_FRAMEWORK_RUNTIME environment variable for this application. - * @param[in] rpathDir Reference of the path directing to the SDV V-API Framework directory. + * @param[in] rpathDir Reference of the path directing to the SDV Vehicle API Framework directory. */ static void SetFrameworkRuntimeDirectory(const std::filesystem::path& rpathDir) { @@ -232,26 +235,30 @@ namespace sdv /** * @brief Get the SDV_COMPONENT_INSTALL environment variable for this application. - * @return Path directing to the SDV V-API component installation directory if available or an empty path if not. + * @remarks If the environment variable is empty, the runtime directory is taken. If the variable contains a relative + * path, the path will be enhanced with the application path. + * @return Absolute path directing to the SDV Vehicle API component installation directory. */ static std::filesystem::path GetComponentInstallDirectory() { + std::filesystem::path path; #ifdef _WIN32 const wchar_t* szComponentDir = _wgetenv(L"SDV_COMPONENT_INSTALL"); - if (!szComponentDir) return {}; - return szComponentDir; + if (szComponentDir) path = szComponentDir; #elif defined __unix__ const char* szComponentDir = getenv("SDV_COMPONENT_INSTALL"); - if (!szComponentDir) return {}; - return szComponentDir; + if (szComponentDir) path = szComponentDir; #else #error The OS is not supported! #endif + if (path.empty()) return GetFrameworkRuntimeDirectory(); + if (path.is_relative()) path = GetAppDirectory() / path; + return path.lexically_normal(); } /** * @brief Set or overwrite the SDV_COMPONENT_INSTALL environment variable for this application. - * @param[in] rpathDir Reference of the path directing to the SDV V-API component installation directory. + * @param[in] rpathDir Reference of the path directing to the SDV Vehicle API component installation directory. */ static void SetComponentInstallDirectory(const std::filesystem::path& rpathDir) { @@ -527,8 +534,7 @@ namespace sdv EAppOperationState m_eState = EAppOperationState::not_started; ///< Application state. EAppContext m_eContext = EAppContext::no_context; ///< Application context. uint32_t m_uiInstanceID = 0u; ///< Core instance. - uint32_t m_uiRetries = 0u; ///< Number of retries to establish a connection. - sdv::TObjectPtr m_ptrServerRepository; ///< Server repository interface. + uint32_t m_uiConnectRetries = 0u; ///< Number of retries to establish a connection. }; } // namespace app } // namespace sdv diff --git a/export/support/component_impl.h b/export/support/component_impl.h index 3a39a92..065b8ad 100644 --- a/export/support/component_impl.h +++ b/export/support/component_impl.h @@ -20,6 +20,8 @@ #include #include #include +#include +#include #include "../interfaces/core_types.h" #include "../interfaces/core.h" @@ -683,9 +685,9 @@ namespace sdv /** * @brief Initialize the object. Overload of IObjectControl::Initialize. - * @param[in] ssObjectConfig Optional configuration string. + * @param[in] sObjectInfo The registration information of this object. */ - virtual void Initialize(/*in*/ const u8string& ssObjectConfig) override + virtual void Initialize(/*in*/ const SObjectInfo& sObjectInfo) override { // Not started before or ended completely? if (GetObjectState() != EObjectState::initialization_pending && GetObjectState() != EObjectState::destruction_pending) @@ -700,19 +702,19 @@ namespace sdv // Initialize the parameter map. InitParamMap(); - // Copy the configuration - m_ssObjectConfig = ssObjectConfig; + // Copy the object information + m_sSelf = sObjectInfo; // Prepare initialization OnPreInitialize(); // Parse the object configuration if there is any. - if (!ssObjectConfig.empty()) + if (!m_sSelf.ssConfig.empty()) { try { // Parse the config TOML. - toml::CTOMLParser parser(ssObjectConfig); + toml::CTOMLParser parser(m_sSelf.ssConfig); if (!parser.IsValid()) { m_eObjectState = EObjectState::config_error; @@ -781,6 +783,15 @@ namespace sdv return m_eObjectState; } + /** + * @brief Get information about ourself. + * @return The object information about ourself. + */ + const SObjectInfo& Self() const + { + return m_sSelf; + } + /** * @brief Set the component operation mode. Overload of IObjectControl::SetOperationMode. * @param[in] eMode The operation mode, the component should run in. @@ -831,15 +842,11 @@ namespace sdv } /** - * @brief Get the object configuration for persistence. + * @brief Build the object configuration from the parameter map and the stored initial configuration. * @return The object configuration as TOML string. */ - virtual u8string GetObjectConfig() const override + virtual u8string BuildObjectConfig() const { - // During the initialization, return the stored object configuration. - if (m_eObjectState == EObjectState::initializing || m_eObjectState == EObjectState::initialization_pending) - return m_ssObjectConfig; - // Split path function (splits the group from the parameter names) auto fnSplitPath = [](const u8string& rssPath) -> std::pair { @@ -849,13 +856,10 @@ namespace sdv return std::make_pair(rssPath.substr(0, nPos), rssPath.substr(nPos + 1)); }; - // Create a new configuration string from the parameters. - auto seqParameters = GetParamPaths(); - if (seqParameters.empty()) return {}; - // Iterate through the list of parameter names and create the TOML entries for it. + auto seqParameters = GetParamPaths(); u8string ssGroup; - toml::CTOMLParser parser(""); + toml::CTOMLParser parser(m_sSelf.ssConfig); toml::CNodeCollection table(parser); for (auto ssParamPath : seqParameters) { @@ -863,7 +867,7 @@ namespace sdv auto ptrParam = FindParamObject(ssParamPath); // Read only and temporary parameters are not stored - if ((!ptrParam->Locked() && ptrParam->ReadOnly()) || !ptrParam->Temporary()) + if ((!ptrParam->Locked() && ptrParam->ReadOnly()) || ptrParam->Temporary()) continue; // Get the value @@ -877,7 +881,9 @@ namespace sdv // Need to add a group? if (prParam.first != ssGroup) { - table = parser.AddTable(prParam.first); + table = parser.GetDirect(prParam.first); + if (!table) + table = parser.AddTable(prParam.first); ssGroup = prParam.first; } @@ -888,6 +894,20 @@ namespace sdv return parser.GetTOML(); } + /** + * @brief Get the object configuration for persistence. Overload of IObjectControl::GetObjectConfig. + * @return The object configuration as TOML string. + */ + virtual u8string GetObjectConfig() const override + { + // During the initialization, return the stored object configuration. + if (m_eObjectState == EObjectState::initializing || m_eObjectState == EObjectState::initialization_pending) + return m_sSelf.ssConfig; + + + return BuildObjectConfig(); + } + /** * @brief Shutdown called before the object is destroyed. Overload of IObjectControl::Shutdown. * @attention Implement calls to other SDV objects here as this is no longer considered safe during the destructor of the @@ -930,7 +950,7 @@ namespace sdv * @post Reset by switching to configuration mode. */ void SetObjectIntoRuntimeErrorState() - { + { if (m_eObjectState == EObjectState::running) m_eObjectState = EObjectState::runtime_error; } @@ -992,6 +1012,12 @@ namespace sdv */ virtual void OnShutdown() {} + /** + * @brief Last function called before destruction. The object integrity is still in tact. It can be assumed that all + * dependencies to the object have been released. + */ + virtual void OnDestroy() {} + /** * @brief Interface map */ @@ -1002,7 +1028,8 @@ namespace sdv private: std::atomic m_eObjectState = EObjectState::initialization_pending; ///< Object state - std::string m_ssObjectConfig; ///< Copy of the configuration TOML. + CLifetimeCookie m_lifetime = CreateLifetimeCookie(); ///< Manage module lifetime. + SObjectInfo m_sSelf; ///< Information about ourself. }; /** @@ -1022,6 +1049,206 @@ namespace sdv END_SDV_INTERFACE_MAP() }; + /** + * @brief Lifetime control implementation class. + */ + class CLifetimeControlImpl : public virtual IInterfaceAccess, public IObjectLifetime, protected IObjectDestroy + { + public: + /** + * @brief Default constructor + */ + CLifetimeControlImpl() + {} + + /** + * @brief Deleted copy constructor. + * @param[in] rClass Reference to the class to copy from. + */ + CLifetimeControlImpl(const CLifetimeControlImpl& rClass) = delete; + + /** + * @brief Deleted move constructor. + * @param[in] rClass Reference to the class to copy from. + */ + CLifetimeControlImpl(CLifetimeControlImpl&& rClass) = delete; + + /** + * @brief Destructor. + */ + virtual ~CLifetimeControlImpl() + {} + + // Interface map (IObjectDestroy is not exposed) + BEGIN_SDV_INTERFACE_MAP() + SDV_INTERFACE_ENTRY(IObjectLifetime) + END_SDV_INTERFACE_MAP() + + /** + * @brief Deleted copy assignment constructor. + * @param[in] rClass Reference to the class to copy from. + * @return Reference to this class. + */ + CLifetimeControlImpl& operator=(const CLifetimeControlImpl& rClass) = delete; + + /** + * @brief Deleted move assignment constructor. + * @param[in] rClass Reference to the class to copy from. + * @return Reference to this class. + */ + CLifetimeControlImpl& operator=(CLifetimeControlImpl&& rClass) = delete; + + /** + * @brief Increment the lifetime. Needs to be balanced by a call to Decrement. Overload of IObjectLifetime::Increment. + */ + virtual void Increment() override + { + m_uiLifetimeCnt++; + } + + /** + * @brief Decrement the lifetime. If the lifetime reaches zero, the object will be destroyed (through the exposed + * IObjectDestroy interface). Overload of IObjectLifetime::Decrement. + * @return Returns 'true' if the object was destroyed, false if not. + */ + virtual bool Decrement() override + { + if (m_uiLifetimeCnt && !(--m_uiLifetimeCnt)) + { + DestroyObject(); + return true; + } + return false; + } + + /** + * @brief Get the current lifetime count. Overload of IObjectLifetime::GetCount. + * @remarks The GetCount function returns a momentary value, which can be changed at any moment. + * @return Returns the current counter value. + */ + virtual uint32_t GetCount() const override + { + return m_uiLifetimeCnt; + } + + protected: + /** + * @brief Destroy the object. Default implementation deletes 'this'. Overload of IObjectDestroy::DestroyObject. + * @attention After a call of this function, all exposed interfaces render invalid and should not be used any more. + */ + virtual void DestroyObject() override + { + delete this; + } + + private: + CLifetimeCookie m_lifetime = CreateLifetimeCookie(); ///< Module lifetime + std::atomic_uint32_t m_uiLifetimeCnt = 1; ///< Lifetime reference counter + }; + + /** + * @brief Lifetime control implementation class based on shared pointer. + * @tparam TClass The type of class deriving CSharedLifetimeControlImpl class. + */ + template + class CSharedLifetimeControlImpl : public std::enable_shared_from_this, public CLifetimeControlImpl + { + public: + /** + * @brief Default constructor + */ + CSharedLifetimeControlImpl() + {} + + /** + * @brief Deleted copy constructor. + * @param[in] rClass Reference to the class to copy from. + */ + CSharedLifetimeControlImpl(const CSharedLifetimeControlImpl& rClass) = delete; + + /** + * @brief Deleted move constructor. + * @param[in] rClass Reference to the class to copy from. + */ + CSharedLifetimeControlImpl(CSharedLifetimeControlImpl&& rClass) = delete; + + /** + * @brief Destructor. + */ + virtual ~CSharedLifetimeControlImpl() + { + } + + // Interface map (IObjectDestroy is not exposed) + BEGIN_SDV_INTERFACE_MAP() + SDV_INTERFACE_ENTRY(IObjectLifetime) + END_SDV_INTERFACE_MAP() + + /** + * @brief Deleted copy assignment constructor. + * @param[in] rClass Reference to the class to copy from. + * @return Reference to this class. + */ + CSharedLifetimeControlImpl& operator=(const CSharedLifetimeControlImpl& rClass) = delete; + + /** + * @brief Deleted move assignment constructor. + * @param[in] rClass Reference to the class to copy from. + * @return Reference to this class. + */ + CSharedLifetimeControlImpl& operator=(CSharedLifetimeControlImpl&& rClass) = delete; + + /** + * @brief Increment the lifetime. Needs to be balanced by a call to Decrement. Overload of IObjectLifetime::Increment. + */ + virtual void Increment() override + { + if (!m_uiLifetimeCnt++) + m_ptrLocking = std::enable_shared_from_this::shared_from_this(); + } + + /** + * @brief Decrement the lifetime. If the lifetime reaches zero, the object will be destroyed (through the exposed + * IObjectDestroy interface). Overload of IObjectLifetime::Decrement. + * @return Returns 'true' if the object was destroyed, false if not. + */ + virtual bool Decrement() override + { + if (m_uiLifetimeCnt && !(--m_uiLifetimeCnt)) + { + DestroyObject(); + return true; + } + return false; + } + + /** + * @brief Get the current lifetime count. Overload of IObjectLifetime::GetCount. + * @remarks The GetCount function returns a momentary value, which can be changed at any moment. + * @return Returns the current counter value. + */ + virtual uint32_t GetCount() const override + { + return m_uiLifetimeCnt; + } + + protected: + /** + * @brief Destroy the object. Default implementation deletes 'this'. Overload of IObjectDestroy::DestroyObject. + * @attention After a call of this function, all exposed interfaces render invalid and should not be used any more. + */ + virtual void DestroyObject() override + { + // Reset the locking; this might delete the class if no other references are present. + m_ptrLocking.reset(); + } + + private: + std::shared_ptr m_ptrLocking; ///< Holder of the instance of the class during lifetime. + CLifetimeCookie m_lifetime = CreateLifetimeCookie(); ///< Module lifetime + std::atomic_uint32_t m_uiLifetimeCnt = 0; ///< Lifetime reference counter + }; + ///////////////////////////////////////// // Object control class implementation // ///////////////////////////////////////// @@ -1114,6 +1341,7 @@ namespace sdv auto objectPtr = std::move(*iter); m_vecActiveObjects.erase(iter); lockObjects.unlock(); + objectPtr->OnDestroy(); objectPtr = nullptr; if (m_uiActiveObjectCount) --m_uiActiveObjectCount; return; diff --git a/export/support/interface_ptr.h b/export/support/interface_ptr.h index e10b40b..9249a2f 100644 --- a/export/support/interface_ptr.h +++ b/export/support/interface_ptr.h @@ -389,7 +389,13 @@ namespace sdv template TIfc* GetInterface() const { - return m_pInterface ? m_pInterface.load()->GetInterface(GetInterfaceId()).template get() : nullptr; + try + { + return m_pInterface ? m_pInterface.load()->GetInterface(GetInterfaceId()).template get() : nullptr; + } catch (const XSysExcept&) + { + return nullptr; + } } private: diff --git a/export/support/local_service_access.h b/export/support/local_service_access.h index 3375315..971a4e6 100644 --- a/export/support/local_service_access.h +++ b/export/support/local_service_access.h @@ -18,10 +18,17 @@ #include "../interfaces/log.h" #include "../interfaces/repository.h" #include "../interfaces/com.h" +#include "../interfaces/app.h" +#include "../interfaces/param.h" +#include "../interfaces/permission.h" #include "interface_ptr.h" +#include +#include +#include #include #include - +#include +#include #ifdef __GNUC__ // Needed for getpid() #include @@ -49,9 +56,11 @@ namespace sdv */ inline TInterfaceAccessPtr GetObject(const std::string& rssObjectName) { - if (!GetCore()) return nullptr; + if (!GetCore()) + return nullptr; IObjectAccess* pRepository = GetCore(); - if (!pRepository) return nullptr; + if (!pRepository) + return nullptr; return pRepository->GetObject(rssObjectName); } @@ -63,9 +72,11 @@ namespace sdv */ inline TInterfaceAccessPtr GetObject(TObjectID tObjectID) { - if (!GetCore()) return nullptr; + if (!GetCore()) + return nullptr; IObjectAccess* pRepository = GetCore(); - if (!pRepository) return nullptr; + if (!pRepository) + return nullptr; return pRepository->GetObjectByID(tObjectID); } #else @@ -113,21 +124,23 @@ namespace sdv } /** - * @brief Log function enables logging for SDV. - * @param[in] eSeverity Severity level of the log message which will be logged, e.g. Info, Warning, Error etc. - * @param[in] rssSrcFile Name of the file from which the message is logged. - * @param[in] uiSrcLine Line of the file from which the message is logged. - * @param[in] rssMessage Reference to the log message to be logged. - */ + * @brief Log function enables logging for SDV. + * @param[in] eSeverity Severity level of the log message which will be logged, e.g. Info, Warning, Error etc. + * @param[in] rssSrcFile Name of the file from which the message is logged. + * @param[in] uiSrcLine Line of the file from which the message is logged. + * @param[in] rssMessage Reference to the log message to be logged. + */ inline void Log(ELogSeverity eSeverity, const u8string& rssSrcFile, uint32_t uiSrcLine, const u8string& rssMessage) { ILogger* pLogger = GetCore() ? GetCore() : nullptr; #ifdef _WIN32 - if (pLogger) pLogger->Log(eSeverity, rssSrcFile, uiSrcLine, _getpid(), "", rssMessage); + if (pLogger) + pLogger->Log(eSeverity, rssSrcFile, uiSrcLine, _getpid(), "", rssMessage); #elif defined __unix__ - if (pLogger) pLogger->Log(eSeverity, rssSrcFile, uiSrcLine, getpid(), "", rssMessage); + if (pLogger) + pLogger->Log(eSeverity, rssSrcFile, uiSrcLine, getpid(), "", rssMessage); #else -#error The OS is currently not supported! + #error The OS is currently not supported! #endif } @@ -137,34 +150,34 @@ namespace sdv */ #define SDV_LOG(severity, ...) sdv::core::internal::CSDVLogImpl(severity, __FILE__, __LINE__, __VA_ARGS__) - /** + /** * @brief Log a trace message with line and position. */ #define SDV_LOG_TRACE(...) sdv::core::internal::CSDVLogImpl(sdv::core::ELogSeverity::trace, __FILE__, __LINE__, __VA_ARGS__) - /** - * @brief Log a debug message with line and position. - */ + /** + * @brief Log a debug message with line and position. + */ #define SDV_LOG_DEBUG(...) sdv::core::internal::CSDVLogImpl(sdv::core::ELogSeverity::debug, __FILE__, __LINE__, __VA_ARGS__) - /** - * @brief Log an information message with line and position. - */ + /** + * @brief Log an information message with line and position. + */ #define SDV_LOG_INFO(...) sdv::core::internal::CSDVLogImpl(sdv::core::ELogSeverity::info, __FILE__, __LINE__, __VA_ARGS__) - /** - * @brief Log a warning message with line and position. - */ + /** + * @brief Log a warning message with line and position. + */ #define SDV_LOG_WARNING(...) sdv::core::internal::CSDVLogImpl(sdv::core::ELogSeverity::warning, __FILE__, __LINE__, __VA_ARGS__) - /** - * @brief Log an error message with line and position. - */ + /** + * @brief Log an error message with line and position. + */ #define SDV_LOG_ERROR(...) sdv::core::internal::CSDVLogImpl(sdv::core::ELogSeverity::error, __FILE__, __LINE__, __VA_ARGS__) - /** - * @brief Log a fatal message with line and position. - */ + /** + * @brief Log a fatal message with line and position. + */ #define SDV_LOG_FATAL(...) sdv::core::internal::CSDVLogImpl(sdv::core::ELogSeverity::fatal, __FILE__, __LINE__, __VA_ARGS__) namespace internal @@ -176,22 +189,22 @@ namespace sdv * @param[in] uiSrcLine Line of the file from which the message is logged. Specified by C++ standard. * @param[in] ...args identifier specified by C++ standard that uses the ellipsis notation in the parameters. */ - template - inline void CSDVLogImpl(ELogSeverity eSeverity, const char* szSrcFile, uint32_t uiSrcLine, Args&& ...args) + template + inline void CSDVLogImpl(ELogSeverity eSeverity, const char* szSrcFile, uint32_t uiSrcLine, Args&&... args) { std::ostringstream stream; (stream << ... << std::forward(args)); Log(eSeverity, szSrcFile ? szSrcFile : "", uiSrcLine, stream.str().c_str()); } - } + } // namespace internal /** * @brief Create a utility * @param[in] rssUtilityName Reference to the utility name. * @param[in] rssUtilityConfig Optional reference to the utility configuration. * @return Smart pointer to the utility or NULL when the utility could not be found. - */ + */ inline TObjectPtr CreateUtility(const std::string& rssUtilityName, const std::string& rssUtilityConfig = std::string()) { TInterfaceAccessPtr ptrRepository = GetObject("RepositoryService"); @@ -199,8 +212,529 @@ namespace sdv if (!pUtilityCreate) return nullptr; return pUtilityCreate->CreateUtility(rssUtilityName, rssUtilityConfig); } - } // namespace core + /** + * @brief Get a parameter from the object smart pointer. + * @param[in] pParameters Pointer to the parameter map interface of an object. + * @param[in] rssParamName Name of the parameter (incl. groups preceding the name and separated with a dot). + * @param[in] bNoExcept When set, do not trigger an std::runtime exception when the object or the parameter could not be + * found. + * @return Returns the value of the parameter or an empty any_t when the object or the parameter could not be found and + * bNoExcept was set. + */ + inline any_t GetParameter(const IParameters* pParameters, const std::string& rssParamName, bool bNoExcept = true) + { + if (!pParameters) + { + if (bNoExcept) return {}; + throw std::runtime_error("The object doesn't expose IParameters interface."); + } + any_t any = pParameters->GetParam(rssParamName); + if (any.empty()) + { + if (bNoExcept) return {}; + throw std::runtime_error("The parameter could not be found."); + } + return any; + } + + /** + * @brief Get a parameter from the parameter interface. + * @param[in] rptrObject Reference to object smart pointer to the the parameter of. + * @param[in] rssParamName Name of the parameter (incl. groups preceding the name and separated with a dot). + * @param[in] bNoExcept When set, do not trigger an std::runtime exception when the object or the parameter could not be + * found. + * @return Returns the value of the parameter or an empty any_t when the object or the parameter could not be found and + * bNoExcept was set. + */ + inline any_t GetParameter(const TInterfaceAccessPtr& rptrObject, const std::string& rssParamName, bool bNoExcept = true) + { + const IParameters* pParameters = rptrObject.GetInterface(); + if (!pParameters) + { + if (bNoExcept) return {}; + throw std::runtime_error("The object doesn't expose IParameters interface."); + } + return GetParameter(pParameters, rssParamName, bNoExcept); + } + + /** + * @brief Get a parameter from the object with the supplied name. + * @param[in] rssObjectName Reference to the string containing the name of the object or service. + * @param[in] rssParamName Name of the parameter (incl. groups preceding the name and separated with a dot). + * @param[in] bNoExcept When set, do not trigger an std::runtime exception when the object or the parameter could not be + * found. + * @return Returns the value of the parameter or an empty any_t when the object or the parameter could not be found and + * bNoExcept was set. + */ + inline any_t GetParameter(const std::string& rssObjectName, const std::string& rssParamName, bool bNoExcept = true) + { + TInterfaceAccessPtr ptrObject = GetObject(rssObjectName); + if (!ptrObject) + { + if (bNoExcept) return {}; + throw std::runtime_error("An object with the name '" + rssObjectName + "' could not be found."); + } + return GetParameter(ptrObject, rssParamName, bNoExcept); + } + + /** + * @brief Get a parameter from the object with the supplied name. Resolve enums and bitmask objects in the returned + * parameter string. + * @param[in] rssObjectName Reference to the string containing the name of the object or service. + * @param[in] rssParamName Name of the parameter (incl. groups preceding the name and separated with a dot). + * @param[in] bNoExcept When set, do not trigger an std::runtime exception when the object or the parameter could not be + * found. + * @return Returns the value of the parameter as a text string or an empty string when the object or the parameter could + * not be found and bNoExcept was set. + */ + inline std::string GetParameterExpand(const std::string& rssObjectName, const std::string& rssParamName, bool bNoExcept = true) + { + TInterfaceAccessPtr ptrObject = GetObject(rssObjectName); + if (!ptrObject) + { + if (bNoExcept) return {}; + throw std::runtime_error("An object with the name '" + rssObjectName + "' could not be found."); + } + + const IParameters* pParameters = ptrObject.GetInterface(); + if (!pParameters) + { + if (bNoExcept) return {}; + throw std::runtime_error("The object doesn't expose IParameters interface."); + } + any_t any = pParameters->GetParam(rssParamName); + if (any.empty()) + { + if (bNoExcept) return {}; + throw std::runtime_error("The parameter could not be found."); + } + + SParamInfo sParamInfo = pParameters->GetParamInfo(rssParamName); + if (sParamInfo.get_switch() == EParamType::enum_param) + { + for (const SLabelInfo::SLabel& rsLabel : sParamInfo.uExtInfo.sEnumInfo.seqLabels) + { + if (rsLabel.anyValue == any) + return rsLabel.ssLabel; + } + } + if (sParamInfo.get_switch() == EParamType::bitmask_param) + { + uint64_t uiValue = static_cast(any); + std::stringstream sstreamValue; + for (const SLabelInfo::SLabel& rsLabel : sParamInfo.uExtInfo.sBitmaskInfo.seqLabels) + { + uint64_t uiValueLabel = static_cast(rsLabel.anyValue); + if (uiValue & uiValueLabel) + { + if (sstreamValue.rdbuf()->in_avail()) // Has characters + sstreamValue << "|"; + sstreamValue << rsLabel.ssLabel; + uiValue = uiValue & ~uiValueLabel; + } + } + if (uiValue) // Not all bits have labels + { + if (sstreamValue.rdbuf()->in_avail()) // Has characters + sstreamValue << "|"; + sstreamValue << uiValue; + } + return sstreamValue.str(); + } + return any; + } + + /// Internal namespace + namespace internal + { + /** + * @brief Trim whitespace at the begin and end of the text. + * @param[in] rssText Reference to the text to trim whitespace for. + * @return The trimmed text. + */ + inline std::string TrimWhitespace(const std::string& rssText) + { + size_t nFirst = rssText.find_first_not_of(" \t\r\n"); + if (nFirst == std::string::npos) return ""; + size_t nLast = rssText.find_last_not_of(" \t\r\n"); + return rssText.substr(nFirst, (nLast - nFirst + 1)); + } + + /** + * @brief Helper function to safely check if a string contains a valid, unescaped $(...) sequence. + * @param[in] rssText The text to check. + * @return Returns whether the text represents a valid variable. + */ + inline bool ContainsValidVariable(const std::string& rssText) + { + size_t nLength = rssText.length(); + size_t nIndex = 0; + + while (nIndex < nLength) + { + if (rssText[nIndex] == '\\') + { + // Skip the escape and the escaped character + nIndex += 2; + } + else if (rssText[nIndex] == '$') + { + // Check if it forms the start of a token sequence '$( ' + if (nIndex + 1 < nLength && rssText[nIndex + 1] == '(') + { + size_t nClose = rssText.find(')', nIndex + 2); + if (nClose != std::string::npos) + { + // Found a valid structural candidate + return true; + } + } + nIndex++; + } + else + { + nIndex++; + } + } + return false; + } + } // namespace internal + + /** + * @brief Resolves embedded object parameter variables formatted as $(object:param) within a text string. + * @param[in] rssText The source text containing potential variables and backslash escapes. + * @param[in] bRecursive If true, continues resolving until no more unescaped variables remain. + * @param[in] bNoExcept If true, returns an empty string for errors; if false, throws std::runtime_error. + * @return The fully resolved text string. + */ + inline std::string ResolveText(const std::string& rssText, bool bRecursive = true, bool bNoExcept = true) + { + std::string ssCurrentText = rssText; + bool bMayHaveMoreVariables = true; + + while (bMayHaveMoreVariables) + { + bMayHaveMoreVariables = false; + std::string ssResult = ""; + ssResult.reserve(ssCurrentText.length()); // Pre-allocate memory for performance + + size_t nLength = ssCurrentText.length(); + size_t nIndex = 0; + + while (nIndex < nLength) + { + char cCurrent = ssCurrentText[nIndex]; + + // Handle backslash escape sequences + if (cCurrent == '\\') + { + // Check if the next character is a '$' + if (nIndex + 1 < nLength && ssCurrentText[nIndex + 1] == '$') + { + ssResult.push_back('$'); // Strip the escape character, keep the literal '$' + nIndex += 2; + } + else + { + ssResult.push_back('\\'); // Preserve other backslashes for paths + nIndex++; + } + } + // Identify potential variables + else if (cCurrent == '$') + { + // extension 2: Only throw an error or treat as a variable if it is + // explicitly part of a dynamic sequence tracking towards a '$(...)' match. + if (nIndex + 1 >= nLength || ssCurrentText[nIndex + 1] != '(') + { + // It is a loose literal '$' (like currency). Keep it and do not throw. + ssResult.push_back('$'); + nIndex++; + continue; + } + + size_t nVarStart = nIndex + 2; // Position right after "$(" + size_t nVarEnd = ssCurrentText.find(')', nVarStart); + + // Validate that a matching closing parenthesis exists + if (nVarEnd == std::string::npos) + { + if (!bNoExcept) + throw std::runtime_error( + "Format error: Missing closing parenthesis ')' for variable starting at index " + + std::to_string(nIndex)); + return ""; + } + + // Extract the inner payload block + std::string sVariableContent = ssCurrentText.substr(nVarStart, nVarEnd - nVarStart); + size_t nColonPos = sVariableContent.find(':'); + + // Validate that the delimiter ':' separates object and parameter names + if (nColonPos == std::string::npos) + { + if (!bNoExcept) + throw std::runtime_error("Format error: Variable content '" + sVariableContent + + "' lacks an 'object:param' colon separator"); + return ""; + } + + // Extract and clean whitespace before/after tokens, preserving inner spaces + std::string sObjName = internal::TrimWhitespace(sVariableContent.substr(0, nColonPos)); + std::string sParamName = internal::TrimWhitespace(sVariableContent.substr(nColonPos + 1)); + + // Request the parameter. + std::string ssValue = GetParameterExpand(sObjName, sParamName, bNoExcept); + + // Add the parameter to the string + ssResult.append(ssValue); + + nIndex = nVarEnd + 1; // Move parsing index past the ')' + } + else + { + ssResult.push_back(cCurrent); + nIndex++; + } + } + + ssCurrentText = ssResult; + + // Exit immediately if recursive resolution was disabled + if (!bRecursive) break; + + // extension 2: Intelligently analyze if a recursive processing loop is required + if (internal::ContainsValidVariable(ssCurrentText)) + bMayHaveMoreVariables = true; + } + + return ssCurrentText; + } + + /** + * @brief Get the current access permission. + * @return The current access permission. + */ + inline EAccessPermission GetCurrentAccessPermission() + { + IPermissionControl* pPermissionControl = GetCore(); + if (!pPermissionControl) + return EAccessPermission::not_set; + return pPermissionControl->GetCurrentPermission(); + } + + /** + * @brief Get the current transmission from this thread and prepare a transfer to another thread. + * @return Transfer ID identifying the permission of this thread. + */ + inline TPermissionTransferID TransferCurrentPermission() + { + IPermissionControl* pPermissionControl = GetCore(); + if (!pPermissionControl) return 0u; + return pPermissionControl->TransferCurrentPermission(); + } + + /** + * @brief Class managing the lifetime of an access permission. + */ + class CAccessPermission + { + // Friend functions + friend CAccessPermission RestrictAccessPermission(EAccessPermission); + friend CAccessPermission SetAccessPermission(TPermissionTransferID); + + public: + /** + * @brief Default constructor + */ + CAccessPermission() = default; + + private: + /** + * @brief Constructor + * @param[in] tPermissionID The access permission to manage. + */ + CAccessPermission(TPermissionID tPermissionID) : m_tPermissionID(tPermissionID) + {} + + public: + /** + * @brief Copy constructor is deleted. + * @param[in] rPermission Reference to the permission to copy from. + */ + CAccessPermission(const CAccessPermission& rPermission) = delete; + + /** + * @brief Move constructor. + * @param[in] rPermission Reference to the permission to move from. + */ + CAccessPermission(CAccessPermission&& rPermission) : m_tPermissionID(rPermission.m_tPermissionID) + { + rPermission.m_tPermissionID = 0u; + } + + /** + * @brief Destructor + */ + ~CAccessPermission() + { + Release(); + } + + /** + * @brief Copy assignment operator is deleted. + * @param[in] rPermission Reference to the permission to copy from. + * @return Reference to this class. + */ + CAccessPermission& operator=(const CAccessPermission& rPermission) = delete; + + /** + * @brief Move assignment operator. + * @param[in] rPermission Reference to the permission to move from. + * @return Reference to this class. + */ + CAccessPermission& operator=(CAccessPermission&& rPermission) + { + m_tPermissionID = rPermission.m_tPermissionID; + rPermission.m_tPermissionID = 0u; + return *this; + } + + /** + * @brief Boolean operator. + * @return Returns whether a valid permission ID is assigned to this class. + */ + operator bool() const + { + return m_tPermissionID ? true : false; + } + + /** + * @brief Does the access permission class contain a valid permission ID. + * @return Returns whether a valid permission ID is assigned to this class. + */ + bool IsValid() const + { + return m_tPermissionID ? true : false; + } + + /** + * @brief Release the permission. + */ + void Release() + { + if (!m_tPermissionID) return; + IPermissionControl* pPermissionControl = GetCore(); + if (!pPermissionControl) return; + pPermissionControl->ReleaseAccessPermission(m_tPermissionID); + m_tPermissionID = 0u; + } + + private: + TPermissionID m_tPermissionID = 0u; ///< Permission ID to be managed by this class. + }; + + /** + * @brief Restrict the current access permission. + * @param[in] ePermission The permission the access should be restricted to. + * @return The access permission connected to this restriction. The lifetime of the access permission is managed by the + * returned class. + */ + inline CAccessPermission RestrictAccessPermission(EAccessPermission ePermission) + { + IPermissionControl* pPermissionControl = GetCore(); + if (!pPermissionControl) return {}; + return pPermissionControl->RestrictAccessPermission(ePermission); + } + + /** + * @brief Set the access permission from a transfer ID that defined the access permission of a different thread. + * @param[in] tTransferID The ID of the transferred access permission. + * @return The access permission for the current thread. The lifetime of the access permission is managed by the returned + * class. + */ + inline CAccessPermission SetAccessPermission(TPermissionTransferID tTransferID) + { + IPermissionControl* pPermissionControl = GetCore(); + if (!pPermissionControl) return {}; + return pPermissionControl->SetAccessPermission(tTransferID); + } + + /** + * @brief Secure thread implementation based on std::thread transferring the permissions from the creation thread to the + * execution thread. + */ + class secure_thread : public std::thread + { + public: + /** + * @brief Default constructor + */ + secure_thread() = default; + + /** + * @brief Copy constructor is deleted. + * @param[in] rthread Reference to the thread object. + */ + secure_thread(const secure_thread& rthread) = delete; + + /** + * @brief Move constructor. + * @param[in] rthread Reference to the thread object. + */ + secure_thread(secure_thread&& rthread) : std::thread(static_cast(rthread)) + {} + + /** + * @brief Assignment constructor for thread execution. + * @tparam F The function to execute. + * @tparam Args The argument types of the function to execute. + * @param[in] f Reference to the function. + * @param[in] args Reference to zero or more arguments. + */ + template + explicit secure_thread(F&& f, Args&&... args) + { + static_cast(*this) = std::thread( + [](TPermissionTransferID tTransferID, auto&& function, auto&&... arguments) + { + CAccessPermission permission = SetAccessPermission(tTransferID); + std::invoke(std::forward(function), std::forward(arguments)...); + }, TransferCurrentPermission(), std::forward(f), std::forward(args)...); + } + + /** + * @brief Move assignment operator. + * @param[in] rthread Reference to the thread object. + * @return Reference to this object. + */ + secure_thread& operator=(secure_thread&& rthread) + { + static_cast(*this) = static_cast(rthread); + return *this; + } + }; + } // namespace core +}// namespace sdv + +/** + * @{ + * @brief Comparison operators + * @param[in] e1 First permission + * @param[in] e2 Second permission + * @return Result of the comparison + */ +inline bool operator<(sdv::core::EAccessPermission e1, sdv::core::EAccessPermission e2) { return static_cast(e1) < static_cast(e2); } +inline bool operator<=(sdv::core::EAccessPermission e1, sdv::core::EAccessPermission e2) { return static_cast(e1) <= static_cast(e2); } +inline bool operator>(sdv::core::EAccessPermission e1, sdv::core::EAccessPermission e2) { return static_cast(e1) > static_cast(e2); } +inline bool operator>=(sdv::core::EAccessPermission e1, sdv::core::EAccessPermission e2) { return static_cast(e1) >= static_cast(e2); } +/** + * @} + */ + +namespace sdv +{ namespace app { /** @@ -208,11 +742,11 @@ namespace sdv * @param[in] rssAttribute Name of the attribute. * @return The attribute value or an empty any-value if the attribute wasn't found or didn't have a value. */ - inline any_t GetAppAttribute(const std::string& rssAttribute) + inline any_t GetAppSettingsAttribute(const std::string& rssAttribute) { - const IAttributes* ptrAppAttributes = core::GetObject("AppControlService"); - if (!ptrAppAttributes) return {}; - return ptrAppAttributes->Get(rssAttribute); + const IParameters* pParameters = core::GetObject("AppSettingsService"); + if (!pParameters) return {}; + return pParameters->GetParam(rssAttribute); } /** @@ -221,7 +755,7 @@ namespace sdv */ inline bool ConsoleIsSilent() { - return GetAppAttribute("console.info_level") == "silent"; + return GetAppSettingsAttribute("Console.Reporting") == "Silent"; } /** @@ -230,7 +764,7 @@ namespace sdv */ inline bool ConsoleIsVerbose() { - return GetAppAttribute("console.info_level") == "verbose"; + return GetAppSettingsAttribute("Console.Reporting") == "Verbose"; } /** @@ -239,7 +773,7 @@ namespace sdv */ inline uint32_t GetAppInstanceID() { - return GetAppAttribute("app.instance_id"); + return GetAppSettingsAttribute("Application.Instance"); } } // namespace app @@ -247,64 +781,81 @@ namespace sdv { /** * @brief Create a repository connection to a local server. - * @param[in] uiInstanceID Optionally the instance ID of the target system to connect to or 0 (default) to connect to the - * instance identified by app-control. * @param[in] nRetries Number of retries to connect (optional, default 30, minimum 3). * @return Returns a smart pointer to the repository proxy. Disconnection takes place when IObjectDestroy::DestroyObject is * called. */ - inline TObjectPtr ConnectToLocalServerRepository(uint32_t uiInstanceID = 0, size_t nRetries = 30) + inline TObjectPtr ConnectToLocalServerRepository(size_t nRetries = 30) { - com::IClientConnect* pClientConnect = core::GetObject("ConnectionService"); - if (!pClientConnect) + // This function works with main, external and maintenance applications. + const app::IAppContext* pAppContext = core::GetCore(); + if (!pAppContext) return {}; + switch (pAppContext->GetContextType()) { - if (!app::ConsoleIsSilent()) - std::cerr << "ERROR: Could not access the connection service." << std::endl; + case app::EAppContext::main: + case app::EAppContext::external: + case app::EAppContext::maintenance: + break; + default: + return {}; + } + + const sdv::app::IAppConnections* pConnections = core::GetObject("AppSettingsService"); + if (!pConnections) return {}; + std::string ssConnectionConfig = pConnections->GetConnectionConfig("Default"); + if (ssConnectionConfig.empty()) return {}; + + sdv::core::IRepositoryControl* pRepository = core::GetObject("RepositoryService"); + if (!pRepository) return {}; + auto tConnectSvcID = pRepository->CreateObject("ClientConnectService", "ClientDefault", ssConnectionConfig); + if (!tConnectSvcID) return {}; + + sdv::TInterfaceAccessPtr ptrConnectSvc = core::GetObject(tConnectSvcID); + sdv::com::IClientConnect* pClientConnect = ptrConnectSvc.GetInterface(); + if (!pClientConnect) + { + pRepository->DestroyObject("ClientDefault"); return {}; } - // Connect the client to the server and return the server repository interface. - std::string ssConnectString = R"code([Client] -Type = "Local" -)code"; - if (uiInstanceID) - ssConnectString += "Instance = " + std::to_string(uiInstanceID) + R"code( -)code"; try { // Try to connect (30 times with 1 second in between). size_t nCnt = 0; - sdv::TObjectPtr ptrRepository; - while (!ptrRepository && nCnt < nRetries) + sdv::TObjectPtr ptrRemoteRepo; + while (!ptrRemoteRepo && nCnt < std::max(nRetries, static_cast(3u))) { nCnt++; - ptrRepository = pClientConnect->Connect(ssConnectString); - if (!ptrRepository) - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + if (pClientConnect->IsConnected() || pClientConnect->Connect()) + { + ptrRemoteRepo = pClientConnect->GetRemoteRepository(); + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } // Return the result - return ptrRepository; + return ptrRemoteRepo; } catch (const XAccessDenied& /*rExcept*/) { if (!app::ConsoleIsSilent()) std::cout << "Access denied trying to connect to a local repository with server instance ID#" << - (uiInstanceID?uiInstanceID : app::GetAppInstanceID()) << "." << std::endl; + app::GetAppInstanceID() << "." << std::endl; return {}; } catch (const XNotFound& /*rExcept*/) { if (!app::ConsoleIsSilent()) std::cout << "Local repository with server instance ID#" << - (uiInstanceID?uiInstanceID : app::GetAppInstanceID()) << " not found." << std::endl; + app::GetAppInstanceID() << " not found." << std::endl; return {}; } catch (const XInvalidState& rExcept) { if (!app::ConsoleIsSilent()) std::cout << "The local repository with server instance ID#" << - (uiInstanceID?uiInstanceID : app::GetAppInstanceID()) << " is in an invalid state: " << rExcept.what() << + app::GetAppInstanceID() << " is in an invalid state: " << rExcept.what() << std::endl; return {}; } @@ -312,12 +863,11 @@ Type = "Local" { if (!app::ConsoleIsSilent()) std::cout << "Timeout occurred trying to connect to a local repository with server instance ID#" << - (uiInstanceID?uiInstanceID : app::GetAppInstanceID()) << "." << std::endl; + app::GetAppInstanceID() << "." << std::endl; return {}; } } - - } // namespace core + } // namespace com } // namespace sdv #endif // !defined LOCAL_SERVICE_ACCESS_H \ No newline at end of file diff --git a/export/support/param_impl.h b/export/support/param_impl.h index 7344e5a..88f0ab4 100644 --- a/export/support/param_impl.h +++ b/export/support/param_impl.h @@ -435,12 +435,14 @@ namespace sdv * @tparam TInfoConstruct The type of the variables that are provided to the constructor function of the parameter * information. * @param[in] rtVar Reference to the parameter variable. + * @param[in] bReadOnly When set, the parameter is read-only (even when writable) and will not be initialized. * @param[in] bLockable When set, the parameter is lockable. Only use with writable parameters. - * @param[in] bAutoDirty When set, the parameter dirty flag is detected automatically. Only use with writable parameters. + * @param[in] bAutoDirty When set, the parameter dirty flag is detected automatically. Only use with writable + * parameters. * @param[in] tConstruct The construct function arguments. */ template - CParamValue(TVar& rtVar, bool bLockable, bool bAutoDirty, TInfoConstruct... tConstruct); + CParamValue(TVar& rtVar, bool bReadOnly, bool bLockable, bool bAutoDirty, TInfoConstruct... tConstruct); /** * @brief Set a value. Overload of CParamGuardian::Set. @@ -993,7 +995,7 @@ namespace sdv } \ std::vector> vecParamInfo; \ [[maybe_unused]] uint32_t uiFlags = 0; \ - [[maybe_unused]] std::string ssGroup; \ + [[maybe_unused]] std::string ssGroup; \ [[maybe_unused]] bool bLockable = false; /** @@ -1107,8 +1109,9 @@ namespace sdv } \ else \ { \ - std::shared_ptr ptrParamInfo = pObject->RegisterParameter(pObject->var, bLockable, true, name_string, \ - default_val, unit_string, ssGroup, description_string, uiFlags); \ + std::shared_ptr ptrParamInfo = pObject->RegisterParameter(pObject->var, \ + uiFlags & static_cast(::sdv::EParamFlags::read_only), bLockable, true, name_string, default_val, \ + unit_string, ssGroup, description_string, uiFlags); \ if (ptrParamInfo) vecParamInfo.push_back(std::move(ptrParamInfo)); \ } @@ -1126,7 +1129,7 @@ namespace sdv * @param description_string The description of the parameter. */ #define SDV_PARAM_NUMBER_ENTRY(var, name_string, default_val, low_limit, high_limit, unit_string, description_string) \ - { \ + { \ auto prLowLimit = sdv::internal::SLowerLimit() low_limit; \ sdv::any_t anyLower; \ if (prLowLimit.second != sdv::internal::ELimitType::no_limit) anyLower = prLowLimit.first; \ @@ -1144,8 +1147,9 @@ namespace sdv } \ else \ { \ - std::shared_ptr ptrParamInfo = pObject->RegisterParameter(pObject->var, bLockable, true, \ - name_string, default_val, anyLower, prLowLimit.second != sdv::internal::ELimitType::up_to_limit, anyUpper, \ + std::shared_ptr ptrParamInfo = pObject->RegisterParameter(pObject->var, \ + uiFlags & static_cast(::sdv::EParamFlags::read_only), bLockable, true, name_string, default_val, \ + anyLower, prLowLimit.second != sdv::internal::ELimitType::up_to_limit, anyUpper, \ prHighLimit.second != sdv::internal::ELimitType::up_to_limit, unit_string, ssGroup, description_string, \ uiFlags); \ if (ptrParamInfo)vecParamInfo.push_back(std::move(ptrParamInfo)); \ @@ -1181,8 +1185,9 @@ namespace sdv } \ else \ { \ - std::shared_ptr ptrParamInfo = pObject->RegisterParameter(pObject->var, bLockable, true, name_string, \ - default_val, pattern_string, unit_string, ssGroup, description_string, uiFlags); \ + std::shared_ptr ptrParamInfo = pObject->RegisterParameter(pObject->var, \ + uiFlags & static_cast(::sdv::EParamFlags::read_only), bLockable, true, name_string, default_val, \ + pattern_string, unit_string, ssGroup, description_string, uiFlags); \ if (ptrParamInfo) vecParamInfo.push_back(std::move(ptrParamInfo)); \ } @@ -1213,8 +1218,9 @@ namespace sdv } \ else \ { \ - std::shared_ptr ptrParamInfo = pObject->RegisterParameter(pObject->var, bLockable, true, name_string, \ - default_val, sdv::internal::GetLabelMapHelper().GetLabelMap(), ssGroup, description_string, uiFlags); \ + std::shared_ptr ptrParamInfo = pObject->RegisterParameter(pObject->var, \ + uiFlags & static_cast(::sdv::EParamFlags::read_only), bLockable, true, name_string, default_val, \ + sdv::internal::GetLabelMapHelper().GetLabelMap(), ssGroup, description_string, uiFlags); \ if (ptrParamInfo) vecParamInfo.push_back(std::move(ptrParamInfo)); \ } @@ -1238,11 +1244,12 @@ namespace sdv std::vector> vecParamInfoMember; \ if constexpr (bStatic) \ { \ - vecParamInfoMember = sdv::internal::SMemberMap::BuildStatic(); \ + vecParamInfoMember = sdv::internal::SMemberMap>::BuildStatic(); \ } \ else \ { \ - auto ptrMemberMap = std::make_shared>(pObject->member, pObject); \ + auto ptrMemberMap = \ + std::make_shared>>(pObject->member, pObject); \ if (ptrMemberMap) \ { \ ptrMemberMap->BuildMap(); \ @@ -1388,13 +1395,15 @@ namespace sdv * @tparam TVar Type of the variable. * @tparam TConstruct The arguments for the parameter info construct function. * @param[in] rtVar Reference to the parameter. + * @param[in] bReadOnly When set, the parameter is read only and will not be initialized. * @param[in] bLockable When set, the parameter is lockable. Only use with writable parameters. * @param[in] bAutoDirty When set, the parameter dirty flag is detected automatically. Only use with writable parameters. * @param[in] tConstruct The construct function arguments. * @return Smart pointer to the parameter information structure. */ template - std::shared_ptr RegisterParameter(TVar& rtVar, bool bLockable, bool bAutoDirty, TConstruct... tConstruct); + std::shared_ptr RegisterParameter(TVar& rtVar, bool bReadOnly, bool bLockable, bool bAutoDirty, + TConstruct... tConstruct); /** * @brief Register a member parameter map into this parameter map. diff --git a/export/support/param_impl.inl b/export/support/param_impl.inl index 83a6d67..9da6136 100644 --- a/export/support/param_impl.inl +++ b/export/support/param_impl.inl @@ -412,13 +412,14 @@ namespace sdv template template - inline CParamValue::CParamValue(TVar& rtVar, bool bLockable, bool bAutoDirty, TInfoConstruct... tConstruct) : + inline CParamValue::CParamValue(TVar& rtVar, bool bReadOnly, bool bLockable, bool bAutoDirty, + TInfoConstruct... tConstruct) : CParamGuardian(bLockable, bAutoDirty, rtVar, tConstruct...), m_rtVar(rtVar) { // Assign the default value if constexpr (!CSdvParamInfo::TypeIsReadOnly()) { - m_rtVar = DefaultVal().get(); + if (!bReadOnly) m_rtVar = DefaultVal().get(); UpdateDirty(DefaultVal()); ResetDirty(); } @@ -678,10 +679,10 @@ namespace sdv } template - inline std::shared_ptr CSdvParamMap::RegisterParameter(TVar& rtVar, bool bLockable, bool bAutoDirty, - TConstruct... tConstruct) + inline std::shared_ptr CSdvParamMap::RegisterParameter(TVar& rtVar, bool bReadOnly, bool bLockable, + bool bAutoDirty, TConstruct... tConstruct) { - auto ptrParam = std::make_shared>(rtVar, bLockable, bAutoDirty, tConstruct...); + auto ptrParam = std::make_shared>(rtVar, bReadOnly, bLockable, bAutoDirty, tConstruct...); m_vecParamMapRegistration.push_back(SParamRegistration(ptrParam)); return ptrParam; } diff --git a/export/support/pssup.inl b/export/support/pssup.inl index ded58f0..997e76c 100644 --- a/export/support/pssup.inl +++ b/export/support/pssup.inl @@ -32,6 +32,7 @@ #include #include #include +#include namespace serdes { @@ -378,7 +379,25 @@ namespace sdv inline CRawDataBypass& GetRawDataBypass() { +#if defined __GNUC__ && defined _WIN32 + // https://dev.azure.com/SW4ZF/AZP-431_DivDI_Vehicle_API/_workitems/edit/610009 + // In MINGW GCC implementation of thread_local, there is an issue with complex classes being used after the destruction. + // This is caused by an architectural bug (https://github.com/msys2/MINGW-packages/issues/2519) causing the emutls (GCC + // Emulated TLS) managing the memory allocation to be executed before the __cxa_thread_exit, the thread cleanup callback + // of Windows. The solution is to use trivial data only (pointer, reference, integers, etc.). A workaround is + // implemented to use a reference of the permission list and map instead of an instance to the list and map. + static auto fnCreateBypass = []() -> CRawDataBypass& + { + static std::mutex mtx; + static std::list lstRawDataBypasses; + std::unique_lock lock(mtx); + lstRawDataBypasses.resize(lstRawDataBypasses.size() + 1); + return lstRawDataBypasses.back(); + }; + thread_local static CRawDataBypass& bypass = fnCreateBypass(); +#else thread_local static CRawDataBypass bypass; +#endif return bypass; } diff --git a/export/support/sdv_core.h b/export/support/sdv_core.h index 470f517..d285bac 100644 --- a/export/support/sdv_core.h +++ b/export/support/sdv_core.h @@ -25,6 +25,7 @@ #include #include #include +#include #ifdef _WIN32 // Resolve conflict @@ -59,6 +60,8 @@ #error OS is not supported! #endif +#include "simple_toml.h" + namespace sdv { /** @@ -148,7 +151,36 @@ namespace sdv{ { /** * @brief The SDV core loader - */ + * @details The SDV core loader class searches for the core library and does the first startup. The core library uses + * the following environment variables to identify its location: + * - SDV_FRAMEWORK_RUNTIME directs to the Vehicle API core location. + * - SDV_COMPONENT_INSTALL directs to the location of the component installations. + * - SDV_FRAMEWORK_DEV_TOOLS directs to the location of the development tools used during the build process of SDV + * components. + * - SDV_FRAMEWORK_DEV_INCLUDE directs to the header file location to allow building components for use with the + * Vehicle API framework. + * + * The SDV core loader supports different core stacks to coexist. For this there is a location procedure for the core + * systems: + * 1. Check for a "sdv_core_reloc.toml" file that contain one or more paths to the core system. The paths defined in the + * file override the global environment variables for this application and all child applications. + * 2. Check for the environment variables. If the SDV_FRAMEWORK_RUNTIM variable is set, it is used to extract all other + * variables as well, if not set. + * 3. If no variable is set, use the location of the EXE as core location. This might work in some situations if the + * path variable has been set properly. + * + * If none of the above strategies work, the core cannot be loaded. + * + * The "sdv_core_reloc.toml" file has the following format: + * @code + * [CoreLocation] + * Version = 100 # Version; currently supported 100 for version 1.0 + * Runtime = "" + * Install = "" # Typically identical to runtime directory + * DevTools = "" # Typically identical to runtime directory + * Include = "" # Typically in the include subdirectory + * @endcode + */ class CSDVCoreLoader { public: @@ -189,77 +221,50 @@ namespace sdv{ { if (m_bInit) return; // Prevent trying to load again. m_bInit = true; - bool bRelocFileError = false; - std::string ssRelocFileInfo = ""; std::string ssEnvironmentInfo = ""; - // Check for the executable directory - std::filesystem::path pathCoreLib; - if (std::filesystem::exists(GetExecDirectory() / "core_services.sdv")) - pathCoreLib = GetExecDirectory() / "core_services.sdv"; + // Step 1: check for the "sdv_core_reloc.toml" + ProcessRelocationFile(); - if (pathCoreLib.empty()) - { - if (std::filesystem::exists(GetExecDirectory() / "sdv_core_reloc.toml")) - { - // Check for the library in the relocation directory - auto relocFolder = GetRelocationPath(); - if (!relocFolder.empty()) - { - auto coreLibFolder = std::filesystem::path(relocFolder) / "core_services.sdv"; - if (coreLibFolder.is_relative()) - coreLibFolder = (GetExecDirectory() / coreLibFolder).lexically_normal(); - - if (std::filesystem::exists(coreLibFolder)) - pathCoreLib = coreLibFolder; - } - if (pathCoreLib.empty()) - { - bRelocFileError = true; // we found the file but not the core library, we must run into an error - ssRelocFileInfo = "Error: Invalid \"sdv_core_reloc.toml\" file found (but no core library), it contains: " + relocFolder; - } - } - } - - if (pathCoreLib.empty()) - { + // Step 2: check for environment variables (either set globally or overwritten by the sdv_core_reloc.toml). #ifdef _WIN32 - std::wstring ssPathCoreTemp(32768, '\0'); - GetEnvironmentVariable(L"SDV_FRAMEWORK_RUNTIME", ssPathCoreTemp.data(), static_cast(ssPathCoreTemp.size())); - ssPathCoreTemp.resize(wcsnlen(ssPathCoreTemp.c_str(), ssPathCoreTemp.size())); - if (!ssPathCoreTemp.empty()) - { - pathCoreLib = std::filesystem::path(ssPathCoreTemp) / "core_services.sdv"; + std::wstring ssPathCoreTemp(32768, '\0'); + GetEnvironmentVariable( + L"SDV_FRAMEWORK_RUNTIME", ssPathCoreTemp.data(), static_cast(ssPathCoreTemp.size())); + ssPathCoreTemp.resize(wcsnlen(ssPathCoreTemp.c_str(), ssPathCoreTemp.size())); + if (!ssPathCoreTemp.empty()) + { + m_pathCoreLib = std::filesystem::path(ssPathCoreTemp) / "core_services.sdv"; #else - std::string ssPathCoreTemp = std::getenv("SDV_FRAMEWORK_RUNTIME") ? std::getenv("SDV_FRAMEWORK_RUNTIME") : ""; - if (!ssPathCoreTemp.empty()) - { - pathCoreLib = std::filesystem::path(ssPathCoreTemp) / "core_services.sdv"; + std::string ssPathCoreTemp = std::getenv("SDV_FRAMEWORK_RUNTIME") ? std::getenv("SDV_FRAMEWORK_RUNTIME") : ""; + if (!ssPathCoreTemp.empty()) + { + m_pathCoreLib = std::filesystem::path(ssPathCoreTemp) / "core_services.sdv"; #endif - if (pathCoreLib.is_relative()) - pathCoreLib = (GetExecDirectory() / pathCoreLib).lexically_normal(); + if (m_pathCoreLib.is_relative()) + m_pathCoreLib = (GetExecDirectory() / m_pathCoreLib).lexically_normal(); - if (!pathCoreLib.empty()) - ssEnvironmentInfo = "System environment path: " + pathCoreLib.generic_string(); - } } - // Depend on system path to find the library - if (pathCoreLib.empty()) - pathCoreLib = "core_services.sdv"; - - // Open the library only if there is no or a valid 'sdv_core_reloc.toml' file - if (!bRelocFileError) + // Step 3: check for the executable directory or otherwise check globally + if (m_pathCoreLib.empty()) { + if (std::filesystem::exists(GetExecDirectory() / "core_services.sdv")) + m_pathCoreLib = GetExecDirectory() / "core_services.sdv"; + else + m_pathCoreLib = "core_services.sdv"; + } + + ssEnvironmentInfo = "System environment path: " + m_pathCoreLib.generic_string(); + #ifdef _WIN32 - SetErrorMode(SEM_FAILCRITICALERRORS); - m_tModule = reinterpret_cast(LoadLibraryW(pathCoreLib.native().c_str())); + SetErrorMode(SEM_FAILCRITICALERRORS); + m_tModule = reinterpret_cast(LoadLibraryW(m_pathCoreLib.native().c_str())); #elif defined __unix__ - m_tModule = reinterpret_cast(dlopen(pathCoreLib.native().c_str(), RTLD_LAZY)); + m_tModule = reinterpret_cast(dlopen(m_pathCoreLib.native().c_str(), RTLD_LAZY)); #else #error OS is not supported! #endif - } if (!m_tModule) { @@ -275,16 +280,12 @@ namespace sdv{ #else #error OS is not supported! #endif - if (ssRelocFileInfo.empty() && ssEnvironmentInfo.empty()) - std::cerr << "No environment variable set and no realocation file found." << std::endl; - if (!ssRelocFileInfo.empty()) - std::cerr << ssRelocFileInfo << std::endl; if (!ssEnvironmentInfo.empty()) std::cerr << ssEnvironmentInfo << std::endl; - - std::cerr << "Could not load \"core_services.sdv\" library"; - if (!ssError.empty()) std::cerr << ": " << ssError; - std::cerr << std::endl; + m_ssErrMsg = "Could not load \"core_services.sdv\" library"; + if (!ssError.empty()) + m_ssErrMsg += ": " + ssError; + std::cerr << m_ssErrMsg << std::endl; return; } @@ -301,7 +302,8 @@ namespace sdv{ #endif if (!fnSDVCore) { - std::cerr << "The library \"core_services.sdv\" doesn't expose the SDVCore function." << std::endl; + m_ssErrMsg = "The library \"core_services.sdv\" doesn't expose the SDVCore function."; + std::cerr << m_ssErrMsg << std::endl; return; } @@ -311,7 +313,8 @@ namespace sdv{ m_pCore = fnSDVCore(); if (!m_pCore) { - std::cerr << "The library \"core_services.sdv\" doesn't provide a valid interface." << std::endl; + m_ssErrMsg = "The library \"core_services.sdv\" doesn't provide a valid interface."; + std::cerr << m_ssErrMsg << std::endl; return; } } @@ -321,7 +324,6 @@ namespace sdv{ */ operator TInterfaceAccessPtr() const { return m_pCore; } - private: /** * @brief Get the directory of the executable. * @return Path to the directory. @@ -346,86 +348,170 @@ namespace sdv{ } /** - * @brief Get the folder path in the file 'sdv_core_reloc.toml'. - * @return Path content, empty string if not found. - */ - static std::string GetRelocationPath() + * @brief Get the path to the core library. + * @return Returns a reference to the path to the core library. + */ + const std::filesystem::path& GetCoreLibPath() const { - if (std::filesystem::exists(GetExecDirectory() / "sdv_core_reloc.toml")) - { - std::ifstream fstream(GetExecDirectory() / "sdv_core_reloc.toml"); - std::string ssLine; - while (std::getline(fstream, ssLine)) - { - size_t nPos = 0; - auto fnSkipWhitespace = [&]() { while (std::isspace(ssLine[nPos])) nPos++; }; - fnSkipWhitespace(); - if (ssLine[nPos] == '#') continue; // Rest of the line is comments - if (ssLine.substr(nPos, 9) != "directory") continue; // not the keq of interest: skip line - nPos += 9; - fnSkipWhitespace(); - if (ssLine[nPos] != '=') - { - std::cout << "Error in \"sdv_core_reloc.toml\": expecting assignment character '=' following" - " keyword 'directory'." << std::endl; - break; - } - nPos++; - fnSkipWhitespace(); - if (ssLine[nPos] != '\"') - { - std::cout << "Error in \"sdv_core_reloc.toml\": expecting double quote character '\"' indicating" - " a string begin'." << std::endl; - break; - } - nPos++; - size_t nStart = nPos; - while (nPos < ssLine.length() && ssLine[nPos] != '\"') - { - // Check for escape character - if (ssLine[nPos] == '\\') nPos++; - - // Skip character - nPos++; - } - if (nPos >= ssLine.length() || ssLine[nPos] != '\"') - { - std::cout << "Error in \"sdv_core_reloc.toml\": expecting double quote character '\"' indicating" - " a string end'." << std::endl; - break; - } - std::string ssDirectory = ssLine.substr(nStart, nPos - nStart); - while (ssDirectory.empty()) - { - std::cout << "Error in \"sdv_core_reloc.toml\": expecting a valid value following the assignment" - " of the 'directory' key." << std::endl; - break; - } - - return ssDirectory; - } - } - return ""; + return m_pathCoreLib; } - bool m_bInit = false; ///< Is the loader initialized? - core::TModuleID m_tModule = 0; ///< Module ID - IInterfaceAccess* m_pCore = nullptr; ///< Pointer to the core services. + /** + * @brief Has loaded successfully? + * @return Returns whether loading was successful. + */ + bool HasLoaded() const + { + return m_bInit && m_tModule && m_pCore; + } + + /** + * @brief Get the error message if loading was unsuccessful. + * @return Returns the a reference to the string containing the error message or an empty string if not available. + */ + const std::string& GetErrorMsg() const + { + return m_ssErrMsg; + } + + private: + /** + * @brief Check for the 'sdv_core_reloc.toml' file containing the information about core relocation. + * @details Set the environment variables following the information from the core relocation file. + */ + static void ProcessRelocationFile() + { + if (!std::filesystem::exists(GetExecDirectory() / "sdv_core_reloc.toml")) return; + + std::ifstream fstream(GetExecDirectory() / "sdv_core_reloc.toml"); + if (!fstream.is_open()) return; + + std::stringstream sstreamContent; + sstreamContent << fstream.rdbuf(); + fstream.close(); + + try + { + sdv::toml::simple_parser::CParser parser(sstreamContent.str()); + if (parser.Root().GetDirect("CoreLocation.Version").GetValue() != SDVFrameworkInterfaceVersion) + { + std::cerr << "Invalid version in sdv_core_reloc.toml" << std::endl; + return; // Version not supported + } + + const auto& sRuntimeNode = parser.Root().GetDirect("CoreLocation.Runtime"); + if (sRuntimeNode) + { + // Note: since the system might load multiple executables and the path is relative to this executable, + // but maybe not to another executable, create an absolute path before assigning the environment + // variable. + std::filesystem::path pathRuntime = sRuntimeNode.GetValue(); + if (pathRuntime.is_relative()) + pathRuntime = (GetExecDirectory() / pathRuntime).lexically_normal(); +#ifdef _WIN32 + // NOTE: In windows there are two environment variables which need to be updated. + std::ignore = SetEnvironmentVariable(L"SDV_FRAMEWORK_RUNTIME", pathRuntime.native().c_str()); + std::ignore = _wputenv((std::wstring(L"SDV_FRAMEWORK_RUNTIME=") + pathRuntime.native()).c_str()); +#elif defined __unix__ + std::ignore = setenv("SDV_FRAMEWORK_RUNTIME", pathRuntime.generic_u8string().c_str(), 1); +#else +#error The OS is not supported! +#endif + } + + const auto& sInstallNode = parser.Root().GetDirect("CoreLocation.Install"); + if (sInstallNode) + { + // Note: since the system might load multiple executables and the path is relative to this executable, + // but maybe not to another executable, create an absolute path before assigning the environment + // variable. + std::filesystem::path pathInstall = sInstallNode.GetValue(); + if (pathInstall.is_relative()) + pathInstall = (GetExecDirectory() / pathInstall).lexically_normal(); +#ifdef _WIN32 + // NOTE: In windows there are two environment variables which need to be updated. + std::ignore = SetEnvironmentVariable(L"SDV_COMPONENT_INSTALL", pathInstall.native().c_str()); + std::ignore = _wputenv((std::wstring(L"SDV_COMPONENT_INSTALL=") + pathInstall.native()).c_str()); +#elif defined __unix__ + std::ignore = setenv("SDV_COMPONENT_INSTALL", pathInstall.generic_u8string().c_str(), 1); +#else +#error The OS is not supported! +#endif + } + + const auto& sDevToolsNode = parser.Root().GetDirect("CoreLocation.DevTools"); + if (sDevToolsNode) + { + // Note: since the system might load multiple executables and the path is relative to this executable, + // but maybe not to another executable, create an absolute path before assigning the environment + // variable. + std::filesystem::path pathDevTools = sDevToolsNode.GetValue(); + if (pathDevTools.is_relative()) + pathDevTools = (GetExecDirectory() / pathDevTools).lexically_normal(); +#ifdef _WIN32 + // NOTE: In windows there are two environment variables which need to be updated. + std::ignore = SetEnvironmentVariable(L"SDV_FRAMEWORK_DEV_TOOLS", pathDevTools.native().c_str()); + std::ignore = _wputenv((std::wstring(L"SDV_FRAMEWORK_DEV_TOOLS=") + pathDevTools.native()).c_str()); +#elif defined __unix__ + std::ignore = setenv("SDV_FRAMEWORK_DEV_TOOLS", pathDevTools.generic_u8string().c_str(), 1); +#else +#error The OS is not supported! +#endif + } + + const auto& sIncludeNode = parser.Root().GetDirect("CoreLocation.Include"); + if (sIncludeNode) + { + // Note: since the system might load multiple executables and the path is relative to this executable, + // but maybe not to another executable, create an absolute path before assigning the environment + // variable. + std::filesystem::path pathInclude = sIncludeNode.GetValue(); + if (pathInclude.is_relative()) + pathInclude = (GetExecDirectory() / pathInclude).lexically_normal(); +#ifdef _WIN32 + // NOTE: In windows there are two environment variables which need to be updated. + std::ignore = SetEnvironmentVariable(L"SDV_FRAMEWORK_DEV_INCLUDE", pathInclude.native().c_str()); + std::ignore = _wputenv((std::wstring(L"SDV_FRAMEWORK_DEV_INCLUDE=") + pathInclude.native()).c_str()); +#elif defined __unix__ + std::ignore = setenv("SDV_FRAMEWORK_DEV_INCLUDE", pathInclude.generic_u8string().c_str(), 1); +#else +#error The OS is not supported! +#endif + } + } + catch (const std::exception&) + {} + } + + bool m_bInit = false; ///< Is the loader initialized? + core::TModuleID m_tModule = 0; ///< Module ID + IInterfaceAccess* m_pCore = nullptr; ///< Pointer to the core services. + std::filesystem::path m_pathCoreLib; ///< Path to the core library. + std::string m_ssErrMsg; ///< Error message for failed loading. }; } // namespace internal #ifndef NO_SDV_CORE_FUNC /** - * @brief Access to the core. - * @return Smart pointer to the core services interface. + * @brief Access to the core loader. + * @return Reference to the one core loader instance. */ - inline TInterfaceAccessPtr GetCore() + inline internal::CSDVCoreLoader& GetCoreLoader() { static internal::CSDVCoreLoader core; core.Load(); return core; } + /** + * @brief Access to the core interfaces. + * @return Smart pointer to the core services interfaces. + */ + inline TInterfaceAccessPtr GetCore() + { + return GetCoreLoader(); + } + /** * @brief Access to specific interface of the core. * @tparam TInterface Type of interface to return. diff --git a/export/support/signal_support.h b/export/support/signal_support.h index 2485105..0e4d490 100644 --- a/export/support/signal_support.h +++ b/export/support/signal_support.h @@ -546,10 +546,10 @@ namespace sdv if (m_ptrValue) m_ptrValue->Receive(anyVal); } - CDispatchService& m_rDispatch; ///< Reference to the dispatch service. - std::function m_funcSignalReceive; ///< Receive signal data - callback function. - IInterfaceAccess* m_pSubscription = nullptr; ///< Cookie received by adding an receive subscription. - std::unique_ptr m_ptrValue; ///< Value to update instead of a callback function. + CDispatchService& m_rDispatch; ///< Reference to the dispatch service. + std::function m_funcSignalReceive; ///< Receive signal data - callback function. + IInterfaceAccess* m_pSubscription = nullptr; ///< Cookie received by adding a subscription. + std::unique_ptr m_ptrValue; ///< Value to update instead of callback func. }; /** diff --git a/export/support/simple_toml.h b/export/support/simple_toml.h new file mode 100644 index 0000000..89db338 --- /dev/null +++ b/export/support/simple_toml.h @@ -0,0 +1,1010 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __GNUC__ +// Some GCC compilers return an overflow warning on string_view functions that use npos as a parameter. This is a known issue +// described here: +// https://stackoverflow.com/questions/79738323/gcc-warning-memcpy-specified-bound-18446744073709551614-exceeds-maximum-objec +// Suppress the warning. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-overflow" +#pragma GCC diagnostic ignored "-Walloc-size-larger-than=" +#pragma GCC diagnostic ignored "-Wstringop-overread" +#endif + +/** + * @brief Simple TOML parser to read keys and tables. This TOML parser can be used to parse a TOML string without the use of the + * SDV framework (e.g. before system startup). + */ +namespace sdv::toml::simple_parser +{ + /** + * @brief Enumeration for all supported TOML node types + */ + enum class ENodeType + { + node_unknown, ///< Node type not valid. + node_table, ///< Node represents a map of key-value pairs + node_array, ///< Node represents a list of elements + node_value ///< Node represents a scalar value (string, number, boolean, etc.) + }; + + // Forward declaration of the main data structure + struct SNode; + + /// Table map alias + using TTableMap = std::map; + + /// Array vector alias + using TNodeArray = std::vector; + + /** + * @brief Main variant structure holding the configuration tree data + */ + struct SNode + { + ENodeType eType = ENodeType::node_unknown; ///< Enumeration Type + std::string ssKey; ///< Name of the value key + std::string ssValueRaw = ""; ///< String raw value + TTableMap mapTable = {}; ///< Map Table + TNodeArray vecArray = {}; ///< Vector Array + + bool bIsInline = false; ///< Locked if defined via inline {...} syntax + bool bIsExplicitlyHeadered = false; ///< Set to true when defined via [header] syntax + + /** + * @brief Helper checking if node is valid. + * @return Returns whether the node is valid (is not unknown). + */ + operator bool() const noexcept + { + return eType != ENodeType::node_unknown; + } + + /** + * @brief Helper checking if node is valid. + * @return Returns whether the node is valid (is not unknown). + */ + bool IsValid() const noexcept + { + return eType != ENodeType::node_unknown; + } + + /** + * @brief Helper checking if node is a table. + * @return Returns whether the node is a table. + */ + bool IsTable() const noexcept + { + return eType == ENodeType::node_table; + } + + /** + * @brief Helper checking if node is an array. + * @return Returns whether the node is an array. + */ + bool IsArray() const noexcept + { + return eType == ENodeType::node_array; + } + + /** + * @brief Helper checking if node is a scalar value. + * @return Returns whether the node is a value node. + */ + bool IsValue() const noexcept + { + return eType == ENodeType::node_value; + } + + /** + * @brief Access function returning the type. + * @return The type of the node. + */ + ENodeType GetType() const noexcept + { + return eType; + } + + /** + * @brief Access function returning the key name. + * @return Reference to the string containing the key name (if not an array member) or an empty string when there is no + * key name. + */ + const std::string& GetName() const noexcept + { + return ssKey; + } + + /** + * @brief Returns the raw value. + * @return Reference to the string containing the raw value of the node. Or returns an empty string when the node is not + * a value node or doesn't contain a value. + */ + const std::string& GetValue() const noexcept + { + return ssValueRaw; + } + + /** + * @brief Return the value converted to the provided type. + * @tparam TType Type to convert to. Supported are integral types, bool, floating point types and std::string. + * @return The result from the conversion or an empty result if the conversion could not be done. + */ + template + TType GetValue() const + { + if constexpr (std::is_same_v) + { + return ssValueRaw == "true" || GetValue() != 0; + } + else if constexpr (std::is_integral_v || std::is_floating_point_v) + { + TType tValue{}; + auto [ptr, ec] = std::from_chars(ssValueRaw.data(), ssValueRaw.data() + ssValueRaw.size(), tValue); + return ec == std::errc() ? tValue : TType{}; + } + else if (std::is_same_v) + { + return ssValueRaw; + } + else + return {}; + } + + /** + * @brief Returns the table map. + * @return If the node is a table, returns a reference to the table map. Or returns an empty map if the node is not a table + * or doesn't contain child nodes. + */ + const TTableMap& GetTable() const noexcept + { + return mapTable; + } + + /** + * @brief Return the array vector. + * @return If the node is an array, returns a reference to the array vector. Or returns an empty vector if the node is not + * an array or doesn't contain any elements. + */ + const TNodeArray& GetArray() const noexcept + { + return vecArray; + } + + /** + * @brief Safe, non-recursive direct lookup using dot and array notation (e.g., "table.array[1].key") + * @param[in] svPath String containing the value path to look for. + * @return Returns a safe, read-only reference to the node of an empty node when not found. + */ + const SNode GetDirect(std::string_view svPath) const noexcept + { + static const SNode sNodeEmpty; + std::reference_wrapper refCurrentNode = *this; + size_t nStart = 0; // Index counter for path segments + + while (nStart < svPath.size()) + { + // 1. Find the next path segment up to the dot delimiter + size_t nDot = svPath.find('.', nStart); + std::string_view svPart = svPath.substr(nStart, nDot == std::string_view::npos ? nDot : nDot - nStart); + + if (svPart.empty()) return sNodeEmpty; + + // 2. Check if the segment contains array brackets [...] (e.g., "arr_mixed[4]") + size_t nOpenBracket = svPart.find('['); + std::string ssKeyLocal(svPart.substr(0, nOpenBracket)); + + // Must be a table node to perform a key lookup + if (!refCurrentNode.get().IsTable()) return sNodeEmpty; + + auto it = refCurrentNode.get().mapTable.find(ssKeyLocal); + if (it == refCurrentNode.get().mapTable.end()) + return sNodeEmpty; + + refCurrentNode = std::cref(it->second); + + // 3. Resolve array indices sequentially (supports multidimensional arrays or inline tables in arrays) + size_t nBracketPos = nOpenBracket; + while (nBracketPos != std::string_view::npos) + { + size_t nCloseBracket = svPart.find(']', nBracketPos); + if (nCloseBracket == std::string_view::npos) + return sNodeEmpty; // Error: Malformed unclosed bracket + + // Extract and parse the index string substring + std::string_view svIndex = svPart.substr(nBracketPos + 1, nCloseBracket - nBracketPos - 1); + + // Ensure the index consists strictly of digits (prevents negative signs or alpha characters) + if (svIndex.empty() || + !std::all_of(svIndex.begin(), svIndex.end(), [](unsigned char c) { return std::isdigit(c); })) + return sNodeEmpty; + + size_t nIdx = 0; + try + { + nIdx = static_cast(std::stoull(std::string(svIndex))); + } + catch (...) + { + return sNodeEmpty; // Safety catch against integer overflow attacks during parsing + } + + // SNode type verification and strict bounds check (Safety-critical constraint) + if (!refCurrentNode.get().IsArray() || nIdx >= refCurrentNode.get().vecArray.size()) + return sNodeEmpty; + + // Advance target reference directly into the array index element + refCurrentNode = std::cref(refCurrentNode.get().vecArray[nIdx]); + + // Check for a consecutive opening bracket immediately following (multidimensional arrays) + nBracketPos = svPart.find('[', nCloseBracket); + } + + // Step over the dot delimiter to move onto the next token sequence + if (nDot == std::string_view::npos) break; + nStart = nDot + 1; + } + return refCurrentNode; + } + }; + + /** + * @brief Main processing unit containing the parsing logic + */ + class CParser + { + public: + /** + * @brief Explicit constructor initializing the parser target view bounds + * @param[in] svInput The TOML string. + */ + // cppcheck-suppress passedByValue + CParser(std::string_view svInput) : m_svSrc(svInput), m_nPos(0) + { + Parse(); + } + + /** + * @brief Get the root node. + * @return Return a reference to the root node. + */ + const SNode& Root() const noexcept + { + return m_sRoot; + } + + private: + std::string_view m_svSrc; ///< Member String View + size_t m_nPos; ///< Member Numerical index) + SNode m_sRoot; ///< Member Object structure + /// Safety Extension: Stores dot-joined path strings of tables defined explicitly via [headers] + std::vector m_vecExplicitlyDefinedTables; + + /** + * @brief Executes the lexical analysis and builds the root node tree + * @return The root node. + */ + SNode Parse() + { + m_sRoot.eType = ENodeType::node_table; + std::vector vCurrentTablePath; + + while (!IsEof()) + { + // Only skip inline whitespace and comments, preserve newlines to check line boundaries + while (!IsEof()) + { + char c = Peek(); + if (c == ' ' || c == '\t' || c == '\r') + { + Consume(); + } + else if (c == '#') + { + while (!IsEof() && Peek() != '\n' && Peek() != '\r') + Consume(); + } + else + { + break; + } + } + if (IsEof()) + break; + + char cNext = Peek(); + if (cNext == '\n') + { + Consume(); // Valid empty line separation + continue; + } + + if (cNext == '[') + { + vCurrentTablePath = ParseTableHeader(); + } + else + { + SNode& oActiveTable = NavigateToTable(vCurrentTablePath); + ParseKeyValue(oActiveTable); + } + + // Safety Constraint: A key-value assignment or header MUST be followed immediately + // by a newline, a comment, or the end of the file. No trailing gibberish allowed on the same line. + while (!IsEof() && (Peek() == ' ' || Peek() == '\t')) + Consume(); + if (!IsEof() && Peek() == '#') + { + while (!IsEof() && Peek() != '\n' && Peek() != '\r') + Consume(); + } + if (!IsEof() && Peek() != '\n' && Peek() != '\r') + { + throw std::runtime_error("Multiple declarations on a single line are strictly invalid in TOML."); + } + } + return m_sRoot; + } + + /** + * @brief Internal safety helper navigating down a string path to return a mutable reference. Automatically diverts into the + * newest element block if tracking a structural Table Array. + * @param[in] rvecPath Reference to vector containing the path + * @return Reference to the node represented by the path. + */ + SNode& NavigateToTable(const std::vector& rvecPath) + { + std::reference_wrapper refCurr = m_sRoot; + for (const auto& rssSection : rvecPath) + { + // Safe traversal step into the standard node map structure + refCurr = std::ref(refCurr.get().mapTable[rssSection]); + + // Context Redirect: If target is a Table Array, dive into the last active table instance + if (refCurr.get().IsArray() && !refCurr.get().vecArray.empty()) + refCurr = std::ref(refCurr.get().vecArray.back()); + } + return refCurr.get(); + } + + /** + * @brief Safety checks ensuring operations remain within string limits + * @return Returns whether the current position exceeds the string size. + */ + bool IsEof() const noexcept + { + return m_nPos >= m_svSrc.size(); + } + + /** + * @brief Non-destructive parsing look-ahead function + * @return The character at the position or a null-character if EOF. + */ + char Peek() const noexcept + { + return IsEof() ? '\0' : m_svSrc[m_nPos]; + } + + /** + * @brief Advances processing cursor forward safely + * @return The character at the position or a null-character if EOF. + */ + char Consume() noexcept + { + return IsEof() ? '\0' : m_svSrc[m_nPos++]; + } + + /** + * @brief Skips standard spacing gaps, multi-line format updates, and hash strings + */ + void SkipWhitespaceAndComments() noexcept + { + while (!IsEof()) + { + char c = Peek(); + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') + { + Consume(); + } + else if (c == '#') + { + while (!IsEof() && Peek() != '\n' && Peek() != '\r') + { + Consume(); + } + } + else + { + break; + } + } + } + + /** + * @brief Skips single inline whitespaces (spaces and tabs) + */ + void SkipInlineWhitespace() noexcept + { + while (!IsEof() && (Peek() == ' ' || Peek() == '\t')) + Consume(); + } + + /** + * @brief Evaluates complex dot-separated key sequences safely + * @return Returns a vector containing the dotted key parts. + */ + std::vector ParseDottedKey() + { + std::vector vecKeys; // Vector of strings + while (!IsEof()) + { + SkipInlineWhitespace(); + std::string ssKey = ""; // String token + char c = Peek(); + if (c == '"' || c == '\'') + ssKey = ParseStringNode(); + else + { + while (!IsEof()) + { + char bc = Peek(); // Bare Char + if (std::isalnum(static_cast(bc)) || bc == '-' || bc == '_') + { + ssKey += Consume(); + } + else + { + break; + } + } + if (ssKey.empty()) + throw std::runtime_error("Empty or invalid key sequence."); + } + vecKeys.push_back(ssKey); + SkipInlineWhitespace(); + if (Peek() == '.') + { + Consume(); + } + else + { + break; + } + } + return vecKeys; + } + + /** + * @brief Handles text string isolation and processes updated escape arrays + * @return The string after parsing. + */ + std::string ParseStringNode() + { + char cQuote = Consume(); // Character delimiter + bool bIsMultiline = false; // Boolean flag + if (Peek() == cQuote) + { + Consume(); + // Warning of cppcheck for the condition to be always true. This is not the case due to the Consume function + // execution. Suppress warning. + // cppcheck-suppress knownConditionTrueFalse + if (Peek() == cQuote) + { + Consume(); + bIsMultiline = true; + } + else + { + return ""; + } + } + + std::string ssResult = ""; + while (!IsEof()) + { + if (bIsMultiline && Peek() == cQuote) + { + if (m_nPos + 2 < m_svSrc.size() && m_svSrc[m_nPos + 1] == cQuote && m_svSrc[m_nPos + 2] == cQuote) + { + m_nPos += 3; + return ssResult; + } + } + else if (!bIsMultiline && Peek() == cQuote) + { + Consume(); + return ssResult; + } + + char c = Consume(); + if (cQuote == '"' && c == '\\') + { + if (IsEof()) + throw std::runtime_error("Unfinished escape sequence."); + char cEsc = Consume(); // Character escape sequence identifier + if (cEsc == 'n') + ssResult += '\n'; + else if (cEsc == 't') + ssResult += '\t'; + else if (cEsc == 'r') + ssResult += '\r'; + else if (cEsc == '"') + ssResult += '"'; + else if (cEsc == '\\') + ssResult += '\\'; + else if (cEsc == 'e') + ssResult += '\x1b'; + else if (cEsc == 'x') + { + if (m_nPos + 1 >= m_svSrc.size()) + throw std::runtime_error("Truncated hex character."); + std::string ssHex{Consume(), Consume()}; + ssResult += static_cast(std::stoi(ssHex, nullptr, 16)); + } + else if (bIsMultiline && (cEsc == '\n' || cEsc == '\r')) + { + if (cEsc == '\r' && Peek() == '\n') + Consume(); + SkipInlineWhitespace(); + } + } + else + { + ssResult += c; + } + } + throw std::runtime_error("Unterminated TOML string sequence detected."); + } + + /** + * @brief Evaluates header configurations structural tables or table arrays ([[table.path]]) + * @return Returns a vector with table headers. + */ + //std::vector ParseTableHeader() + //{ + // Consume(); // Consume standard structural '[' + + // bool bIsTableArray = false; + // if (Peek() == '[') + // { + // Consume(); + // bIsTableArray = true; + // } + + // std::vector vecSections = ParseDottedKey(); + // SkipInlineWhitespace(); + + // if (bIsTableArray) + // { + // if (Consume() != ']' || Consume() != ']') + // throw std::runtime_error("Malformed table array closure."); + // } + // else + // { + // if (Consume() != ']') + // throw std::runtime_error("Malformed table closure."); + // } + + // // Generate a unified lookup string for this table path (e.g., "fruit.apple") + // std::string strFullNormalizedPath = ""; + // for (size_t i = 0; i < vecSections.size(); ++i) + // { + // strFullNormalizedPath += vecSections[i] + (i == vecSections.size() - 1 ? "" : "."); + // } + + // // Safety Constraint: Check if this specific exact table header path was already explicitly declared + // if (!bIsTableArray) + // { + // if (std::find(m_vecExplicitlyDefinedTables.begin(), m_vecExplicitlyDefinedTables.end(), strFullNormalizedPath) + // != m_vecExplicitlyDefinedTables.end()) + // { + // throw std::runtime_error("Duplicate table declaration detected: " + strFullNormalizedPath); + // } + // m_vecExplicitlyDefinedTables.push_back(strFullNormalizedPath); + // } + + // std::reference_wrapper refCurrent = m_sRoot; + // for (size_t i = 0; i < vecSections.size() - 1; ++i) + // { + // const auto& ssSection = vecSections[i]; + + // auto it = refCurrent.get().mapTable.find(ssSection); + // if (it != refCurrent.get().mapTable.end()) + // { + // if (it->second.IsValue()) + // { + // throw std::runtime_error("Cannot re-define a scalar key value as an intermediate table structure."); + // } + // // If it exists but is a literal array (not explicitly headered), collision! + // if (it->second.IsArray() && !it->second.bIsExplicitlyHeadered) + // { + // throw std::runtime_error("Type collision: Cannot append table elements to a literal array."); + // } + // } + // else + // { + // SNode sNewNode; + // sNewNode.eType = ENodeType::node_table; + // sNewNode.ssKey = ssSection; + // refCurrent.get().mapTable[ssSection] = sNewNode; + // } + // refCurrent = std::ref(refCurrent.get().mapTable[ssSection]); + // if (refCurrent.get().IsArray() && !refCurrent.get().vecArray.empty()) + // { + // refCurrent = std::ref(refCurrent.get().vecArray.back()); + // } + // } + + // std::string ssFinalSection = vecSections.back(); + // auto itFinal = refCurrent.get().mapTable.find(ssFinalSection); + + // if (itFinal != refCurrent.get().mapTable.end()) + // { + // if (itFinal->second.IsValue()) + // { + // throw std::runtime_error("Conflict: Table header overrides an existing scalar value."); + // } + // if (bIsTableArray && itFinal->second.IsTable()) + // { + // throw std::runtime_error("Type collision: Static table cannot be turned into a table array."); + // } + // if (!bIsTableArray && itFinal->second.IsArray()) + // { + // if (itFinal->second.bIsExplicitlyHeadered) + // { + // throw std::runtime_error("Type collision: Array of tables cannot be redefined as a standard table."); + // } + // else + // { + // throw std::runtime_error("Type collision: Literal array cannot be redefined as a table."); + // } + // } + + // // CRITICAL FIX: The invalid exception here was thrown when itFinal->second.IsTable() was true + // // but it was implicitly created by a sub-key earlier (e.g., fruit.apple). + // // In TOML, a header can safely claim an implicit table, UNLESS it attempts to redefine a value + // // that is an explicit scalar or another explicit table type. + // } + + // if (bIsTableArray) + // { + // if (refCurrent.get().mapTable.find(ssFinalSection) == refCurrent.get().mapTable.end()) + // { + // SNode sNewArrayNode; + // sNewArrayNode.eType = ENodeType::node_array; + // sNewArrayNode.ssKey = ssFinalSection; + // sNewArrayNode.bIsExplicitlyHeadered = true; // Mark as Table Array type + // refCurrent.get().mapTable[ssFinalSection] = sNewArrayNode; + // } + // SNode sNewTableInstance; + // sNewTableInstance.eType = ENodeType::node_table; + // sNewTableInstance.ssKey = ssFinalSection; + // sNewTableInstance.bIsExplicitlyHeadered = true; + // refCurrent.get().mapTable[ssFinalSection].vecArray.push_back(sNewTableInstance); + // } + // else + // { + // if (refCurrent.get().mapTable.find(ssFinalSection) == refCurrent.get().mapTable.end()) + // { + // SNode sNewNode; + // sNewNode.eType = ENodeType::node_table; + // sNewNode.ssKey = ssFinalSection; + // sNewNode.bIsExplicitlyHeadered = true; + // refCurrent.get().mapTable[ssFinalSection] = sNewNode; + // } + // else + // { + // refCurrent.get().mapTable[ssFinalSection].bIsExplicitlyHeadered = true; + // } + // } + + // return vecSections; + //} + // Evaluates header configurations structural tables or table arrays ([[table.path]]) + std::vector ParseTableHeader() + { + Consume(); // Consume standard structural '[' + + bool bIsTableArray = false; + if (Peek() == '[') + { + Consume(); + bIsTableArray = true; + } + + std::vector vecSections = ParseDottedKey(); + SkipInlineWhitespace(); + + if (bIsTableArray) + { + if (Consume() != ']' || Consume() != ']') + throw std::runtime_error("Malformed table array closure."); + } + else + { + if (Consume() != ']') + throw std::runtime_error("Malformed table closure."); + } + + std::reference_wrapper refCurrent = m_sRoot; + + // 1. Traverse down through intermediate namespaces until the second-to-last token component + for (size_t i = 0; i < vecSections.size() - 1; ++i) + { + const auto& ssSection = vecSections[i]; + + if (refCurrent.get().bIsInline) + { + throw std::runtime_error("Cannot add subtables to an immutable inline table."); + } + + auto it = refCurrent.get().mapTable.find(ssSection); + if (it != refCurrent.get().mapTable.end()) + { + if (it->second.IsValue()) + { + throw std::runtime_error("Cannot re-define a scalar key value as an intermediate table structure."); + } + if (it->second.IsArray() && !it->second.bIsExplicitlyHeadered) + { + throw std::runtime_error("Type collision: Cannot append table elements to a literal array."); + } + } + else + { + SNode sNewNode; + sNewNode.eType = ENodeType::node_table; + sNewNode.ssKey = ssSection; + refCurrent.get().mapTable[ssSection] = sNewNode; + } + + // Advance cursor reference + refCurrent = std::ref(refCurrent.get().mapTable[ssSection]); + + // Context Redirect: Always dive into the newest array element block if tracking a structural Table Array + if (refCurrent.get().IsArray() && !refCurrent.get().vecArray.empty()) + { + refCurrent = std::ref(refCurrent.get().vecArray.back()); + } + } + + // 2. Handle the final explicit destination node leaf signature + std::string ssFinalSection = vecSections.back(); + auto itFinal = refCurrent.get().mapTable.find(ssFinalSection); + + if (itFinal != refCurrent.get().mapTable.end()) + { + if (itFinal->second.IsValue()) + { + throw std::runtime_error("Conflict: Table header overrides an existing scalar value."); + } + if (bIsTableArray && itFinal->second.IsTable()) + { + throw std::runtime_error("Type collision: Static table cannot be turned into a table array."); + } + if (!bIsTableArray && itFinal->second.IsArray()) + { + if (itFinal->second.bIsExplicitlyHeadered) + { + throw std::runtime_error("Type collision: Array of tables cannot be redefined as a standard table."); + } + else + { + throw std::runtime_error("Type collision: Literal array cannot be redefined as a table."); + } + } + + // SAFETY CONSTRAINT REMEDIED: + // Only throw duplicate declaration errors if a standard table header is redefined + // *explicitly* inside the exact same local scope block wrapper. + if (!bIsTableArray && itFinal->second.IsTable() && itFinal->second.bIsExplicitlyHeadered) + { + throw std::runtime_error("Duplicate table declaration detected in this scope: " + ssFinalSection); + } + } + + // 3. Construct or instantiate the final target node leaf elements safely + if (bIsTableArray) + { + if (refCurrent.get().mapTable.find(ssFinalSection) == refCurrent.get().mapTable.end()) + { + SNode sNewArrayNode; + sNewArrayNode.eType = ENodeType::node_array; + sNewArrayNode.ssKey = ssFinalSection; + sNewArrayNode.bIsExplicitlyHeadered = true; + refCurrent.get().mapTable[ssFinalSection] = sNewArrayNode; + } + SNode sNewTableInstance; + sNewTableInstance.eType = ENodeType::node_table; + sNewTableInstance.ssKey = ssFinalSection; + sNewTableInstance.bIsExplicitlyHeadered = true; + refCurrent.get().mapTable[ssFinalSection].vecArray.push_back(sNewTableInstance); + } + else + { + if (refCurrent.get().mapTable.find(ssFinalSection) == refCurrent.get().mapTable.end()) + { + SNode sNewNode; + sNewNode.eType = ENodeType::node_table; + sNewNode.ssKey = ssFinalSection; + sNewNode.bIsExplicitlyHeadered = true; + refCurrent.get().mapTable[ssFinalSection] = sNewNode; + } + else + { + refCurrent.get().mapTable[ssFinalSection].bIsExplicitlyHeadered = true; + } + } + + return vecSections; + } + + /** + * @brief Extracts key assignment links and creates required target structural tables. Works relative to the passed + * rsParentTable node, ensuring proper nesting. + * @param[in] rsParentTable Reference to the parent table. + */ + void ParseKeyValue(SNode& rsParentTable) + { + std::vector vecKeys = ParseDottedKey(); + SkipInlineWhitespace(); + if (Consume() != '=') + throw std::runtime_error("Missing valid variable declaration assignment mapping."); + SkipInlineWhitespace(); + + std::reference_wrapper refTarget = std::ref(rsParentTable); + for (size_t i = 0; i < vecKeys.size() - 1; ++i) + { + // ONLY block if we are attempting to add elements down through a table that was completed + // and locked as an immutable inline structure from an external scope. + if (refTarget.get().bIsInline) + { + throw std::runtime_error("Cannot traverse or append keys through an immutable inline table."); + } + + auto it = refTarget.get().mapTable.find(vecKeys[i]); + if (it != refTarget.get().mapTable.end()) + { + if (it->second.IsValue()) + { + throw std::runtime_error("Collision: Implied intermediate segment conflicts with an existing scalar."); + } + if (it->second.bIsInline) + { + throw std::runtime_error("Collision: Implied path segment penetrates an immutable inline table."); + } + } + else + { + SNode sNewNode; + sNewNode.eType = ENodeType::node_table; + sNewNode.ssKey = vecKeys[i]; + refTarget.get().mapTable[vecKeys[i]] = sNewNode; + } + refTarget = std::ref(refTarget.get().mapTable[vecKeys[i]]); + if (refTarget.get().IsArray() && !refTarget.get().vecArray.empty()) + { + refTarget = std::ref(refTarget.get().vecArray.back()); + } + } + + std::string ssFinalKey = vecKeys.back(); + + // Safety Constraint: Check if the final target key value has already been populated in this specific map context + if (refTarget.get().mapTable.find(ssFinalKey) != refTarget.get().mapTable.end()) + { + throw std::runtime_error("Duplicate key configuration definition detected: " + ssFinalKey); + } + + SNode sParsedValue = ParseValueNode(); + + // Safety Constraint: Catch empty data assignment attempts like `key = # comment` + if (sParsedValue.IsValue() && sParsedValue.ssValueRaw.empty()) + { + throw std::runtime_error("Assignments cannot be empty or contain only comment elements."); + } + + sParsedValue.ssKey = ssFinalKey; + refTarget.get().mapTable[ssFinalKey] = sParsedValue; + } + + /** + * @brief Resolves scalar elements, multi-line inline dictionaries, and arrays + * @return The parsed node. + */ + SNode ParseValueNode() + { + SkipWhitespaceAndComments(); + char c = Peek(); + SNode sValue; + + if (c == '"' || c == '\'') + { + sValue.eType = ENodeType::node_value; + sValue.ssValueRaw = ParseStringNode(); + } + else if (c == '{') + { + sValue.eType = ENodeType::node_table; + Consume(); // Consume starting '{' + while (!IsEof()) + { + SkipWhitespaceAndComments(); + if (Peek() == '}') + { + Consume(); + break; + } + ParseKeyValue(sValue); // Can append safely during the parsing lifecycle phase + SkipWhitespaceAndComments(); + if (Peek() == ',') + { + Consume(); + } + else if (Peek() == '}') + { + Consume(); + break; + } + } + // CRITICAL FIX: Lock the inline structure to prevent outside modifications + // ONLY after it has completely finished parsing and its braces are closed! + sValue.bIsInline = true; + } + else if (c == '[') + { + sValue.eType = ENodeType::node_array; + sValue.bIsExplicitlyHeadered = false; // Literal array token + Consume(); + while (!IsEof()) + { + SkipWhitespaceAndComments(); + if (Peek() == ']') + { + Consume(); + break; + } + sValue.vecArray.push_back(ParseValueNode()); + SkipWhitespaceAndComments(); + if (Peek() == ',') + { + Consume(); + } + else if (Peek() == ']') + { + Consume(); + break; + } + } + } + else + { + sValue.eType = ENodeType::node_value; + std::string strRaw = ""; + while (!IsEof() && Peek() != '\n' && Peek() != '\r' && Peek() != ',' && Peek() != '}' && Peek() != ']' + && Peek() != '#') + { + char rc = Consume(); + if (rc != ' ' && rc != '\t') + strRaw += rc; + } + sValue.ssValueRaw = strRaw; + } + return sValue; + } + }; +} // namespace sdv::toml::simple_parser + +#ifdef __GNUC__ + #pragma GCC diagnostic pop +#endif diff --git a/export/support/toml.h b/export/support/toml.h index 659ad17..c00b3eb 100644 --- a/export/support/toml.h +++ b/export/support/toml.h @@ -11,12 +11,14 @@ * Erik Verhoeven - initial API and implementation ********************************************************************************/ -#ifndef SDV_CONFIG_H -#define SDV_CONFIG_H +#ifndef SDV_TOML_H +#define SDV_TOML_H +#include #include "../interfaces/toml.h" #include "interface_ptr.h" #include "local_service_access.h" +#include "interface_ptr.h" namespace sdv::toml { @@ -82,6 +84,18 @@ namespace sdv::toml */ ENodeType GetType() const; + /** + * @brief Get the index of this node within the parent collection. + * @return The index of the node within the parent collection node or npos when no parent is available. + */ + uint32_t GetIndex() const; + + /** + * @brief Is the node defined as inline node? + * @return The inline flag. + */ + bool IsInline() const; + /** * @brief Return any associated comment text for this node. * @return The node comment or an empty string when there is not comment for this node. @@ -132,6 +146,12 @@ namespace sdv::toml */ virtual void Clear(); + /** + * @brief Format the node automatically, remove redundant whitespace. + * @param[in] bRemoveComments When set, the comments are removed from the node. + */ + void AutomaticFormat(bool bRemoveComments); + /** * @brief Get the TOML string from this node including all children. * @return The TOML string. @@ -228,23 +248,29 @@ namespace sdv::toml */ CNode GetDirect(const sdv::u8string& rssNode) const; + /** + * @brief Return the node name for a node in the collection. If the collection is an array, provides the index between + * square brackets. + * @param[in] nIndex Index to return the node name for. + * @return Name of the node or an empty string if the index is larger than the amount of nodes in the collection. + */ + std::string GetNodeNameByIndex(size_t nIndex) const; + /** * @brief Insert a value node before the provided position. - * @remarks The actual position depends on the type of node and the order the nodes are stored. Inline nodes come before - * standard nodes. - * @param[in] nIndex The index before which to insert the node. Can be larger than the count value as well as - * sdv::toml::npos when adding the node at the end. + * @remarks In TOML, inline nodes are located before standard nodes. Since values are presented as inline node, they + * will be inserted before any standard node (table or table array if defined as standard node). + * @param[in] rssInsertBefore Name of the node to insert the value before. In case of an array, can be an index between + * square brackets. Can be empty, causing the node to be inserted at the end. * @param[in] rssName Reference to the name of the new value node. If this collection is an array, the name is ignored. * Otherwise the name must be unique within this collection. * @param[in] ranyValue The value to assign to the node. The value also determines the type of value node. * @return Returns the node when successfully inserted or an empty node when not. */ - CNode InsertValue(size_t nIndex, const std::string& rssName, const sdv::any_t& ranyValue); + CNode InsertValue(const std::string& rssInsertBefore, const std::string& rssName, const sdv::any_t& ranyValue); /** * @brief Add a value node to the collection. - * @remarks The actual position depends on the type of node and the order the nodes are stored. Inline nodes come before - * standard nodes. * @param[in] rssName Reference to the name of the new array collection node. If this collection is an array, the name is * ignored. Otherwise the name must be unique within this collection. * @param[in] ranyValue The value to assign to the node. The value also determines the type of value node. @@ -254,20 +280,18 @@ namespace sdv::toml /** * @brief Insert an array collection before the provided position. - * @remarks The actual position depends on the type of node and the order the nodes are stored. Inline nodes come before - * standard nodes. - * @param[in] nIndex The index before which to insert the node. Can be larger than the count value as well as - * sdv::toml::npos when adding the node at the end. + * @remarks In TOML, inline nodes are located before standard nodes. Since arrays are presented as inline node, they + * will be inserted before any standard node (table or table array if defined as standard node). + * @param[in] rssInsertBefore Name of the node to insert the value before. In case of an array, can be an index between + * square brackets. Can be empty, causing the node to be inserted at the end. * @param[in] rssName Reference to the name of the new value node. If this collection is an array, the name is ignored. * Otherwise the name must be unique within this collection. * @return Returns the collection node when successfully inserted or an empty node when not. */ - CNodeCollection InsertArray(size_t nIndex, const std::string& rssName); + CNodeCollection InsertArray(const std::string& rssInsertBefore, const std::string& rssName); /** * @brief Add an array collection to this collection. - * @remarks The actual position depends on the type of node and the order the nodes are stored. Inline nodes come before - * standard nodes. * @param[in] rssName Reference to the name of the new array collection node. If this collection is an array, the name is * ignored. Otherwise the name must be unique within this collection. * @return Returns the collection node when successfully added or an empty node when not. @@ -276,22 +300,20 @@ namespace sdv::toml /** * @brief Insert a table collection before the provided position. - * @remarks The actual position depends on the type of node and the order the nodes are stored. Inline nodes come before - * standard nodes. - * @param[in] nIndex The index before which to insert the node. Can be larger than the count value as well as - * sdv::toml::npos when adding the node at the end. + * @remarks In TOML, inline nodes are located before standard nodes. Tables can be inserted as inline node, in which + * case they will be inserted before any standard node (table or table array if defined as standard node). + * @param[in] rssInsertBefore Name of the node to insert the value before. In case of an array, can be an index between + * square brackets. Can be empty, causing the node to be inserted at the end. * @param[in] rssName Reference to the name of the new table collection node. If this collection is an array, the name is * ignored. Otherwise the name must be unique within this collection. * @param[in] bFavorInline When set, the node will be added as inline collection node. When not, the node will be inserted * as inline collection if the this collection is also inline, as standard when not. * @return Returns the collection node when successfully inserted or an empty node when not. */ - CNodeCollection InsertTable(size_t nIndex, const std::string& rssName, bool bFavorInline = false); + CNodeCollection InsertTable(const std::string& rssInsertBefore, const std::string& rssName, bool bFavorInline = false); /** * @brief Add a table collection to this collection. - * @remarks The actual position depends on the type of node and the order the nodes are stored. Inline nodes come before - * standard nodes. * @param[in] rssName Reference to the name of the new table collection node. If this collection is an array, the name is * ignored. Otherwise the name must be unique within this collection. * @param[in] bFavorInline When set, the node will be added as inline collection node. When not, the node will be inserted @@ -302,12 +324,12 @@ namespace sdv::toml /** * @brief Insert a table array collection before the provided position. - * @remarks The actual position depends on the type of node and the order the nodes are stored. Inline nodes come before - * standard nodes. * @remarks A table array is an array with table inside. Inserting a table array node can also be done by creating an array, * if not existing already, and adding a table to the array. - * @param[in] nIndex The index before which to insert the node. Can be larger than the count value as well as - * sdv::toml::npos when adding the node at the end. + * @remarks In TOML, inline nodes are located before standard nodes. Table arrays can be inserted as inline node, in + * which case they will be inserted before any standard node (table or table array if defined as standard node). + * @param[in] rssInsertBefore Name of the node to insert the value before. In case of an array, can be an index between + * square brackets. Can be empty, causing the node to be inserted at the end. * @param[in] rssName Reference to the name of the new table array node. If this collection is an array, the name is * ignored. Otherwise the name must be unique within this collection. * @param[in] bFavorInline When set, the node will be added as inline collection node. When not, the node will be inserted @@ -315,12 +337,10 @@ namespace sdv::toml * @return Returns the collection node when successfully inserted or an empty node when not. The collection node represents * a table collection. */ - CNodeCollection InsertTableArray(size_t nIndex, const std::string& rssName, bool bFavorInline = false); + CNodeCollection InsertTableArray(const std::string& rssInsertBefore, const std::string& rssName, bool bFavorInline = false); /** * @brief Add a table array collection to this collection. - * @remarks The actual position depends on the type of node and the order the nodes are stored. Inline nodes come before - * standard nodes. * @remarks A table array is an array with table inside. Inserting a table array node can also be done by creating an array, * if not existing already, and adding a table to the array. * @param[in] rssName Reference to the name of the new table array node. If this collection is an array, the name is @@ -335,10 +355,10 @@ namespace sdv::toml /** * @brief Insert a TOML string to the collection. All nodes specified in the TOML will be added in the collection except * when the nodes already exist. Comment and whitespace are preserved when possible. - * @remarks The actual position depends on the type of node and the order the nodes are stored. Inline nodes come before - * standard nodes. - * @param[in] nIndex The index before which to insert the node. Can be larger than the count value as well as - * sdv::toml::npos when adding the node at the end. + * @remarks In TOML, inline nodes are located before standard nodes. Dependable on the nodes defined in the TOML they + * might be transferred to inline or they might be inserted at a different location. + * @param[in] rssInsertBefore Name of the node to insert the value before. In case of an array, can be an index between + * square brackets. Can be empty, causing the node to be inserted at the end. * @param[in] rssTOML Reference to the TOML string containing the nodes. The TOML string can be empty, which is not an * error. If required the TOML nodes are converted to inline nodes. * @param[in] bAllowPartial When set, duplicate nodes (already present in this collection) will be ignored and do not @@ -346,7 +366,7 @@ namespace sdv::toml * @return Returns 1 if the complete TOMl could be inserted, 0 if no TOML could be inserted or -1 when the TOML could be * partially inserted. */ - int InsertTOML(size_t nIndex, const std::string& rssTOML, bool bAllowPartial = false); + int InsertTOML(const std::string& rssInsertBefore, const std::string& rssTOML, bool bAllowPartial = false); /** * @brief Add a TOML string to this collection. All nodes specified in the TOML will be added in the collection except @@ -372,16 +392,11 @@ namespace sdv::toml class CTOMLParser : public CNodeCollection { public: - /** - * @brief Default constructor. - */ - CTOMLParser() = default; - /** * @brief Constructor providing automatic processing. * @param[in] rssConfig Reference to the configuration. */ - CTOMLParser(const std::string& rssConfig); + CTOMLParser(const std::string& rssConfig = ""); /** * @brief Process a configuration. This will clear any previous configuration. @@ -414,6 +429,52 @@ namespace sdv::toml ITOMLParser* m_pParser = nullptr; ///< Pointer to the parser interface. }; + /** + * @brief Compare flags + */ + enum class ECompareFlags : uint32_t + { + compare_ignore_whitespace = 1, ///< Compare, but ignore whitespace + compare_ignore_comments = 2, ///< Compare, but ignore comments + compare_ignore_inline = 8, ///< Compare, but ignore inline or explicit + compare_ignore_all = 255, ///< Compare with all ignore flags + }; + + /** + * @brief Compare result + */ + enum class ECompareResult : int32_t + { + compare_identical = 0, ///< The comparison resulted into identical TOML strings + compare_different = 1, ///< The comparison resulted into different TOML strings + compare_error = -1 ///< The comparison could not be done due to a failure + }; + + /// Internal namespace + namespace internal + { + /** + * @brief Compare the content of the node with the content of another node. + * @attention This function changes the nodes dependable on the provided flags. + * @param[in] rnode1 First node interface to compare with the second node. + * @param[in] rnode2 Second node interface to compare with the first node. + * @param[in] uiCompareFlags Zero or more flags from ECompareFlags. + * @return The comparison result. + */ + ECompareResult CompareNodes(CNode& rnode1, CNode& rnode2, + uint32_t uiCompareFlags = static_cast(ECompareFlags::compare_ignore_all)); + } // namespace internal; + + /** + * @brief Compare the content of TOML string with the content of another TOML string. + * @param[in] rssToml1 Reference to the first TOML string to compare with the second TOML string. + * @param[in] rssToml2 Reference to the second TOML string to compare with the first TOML string. + * @param[in] uiCompareFlags Zero or more flags from ECompareFlags. + * @return The comparison result. + */ + ECompareResult Compare(const std::string& rssToml1, const std::string& rssToml2, + uint32_t uiCompareFlags = static_cast(ECompareFlags::compare_ignore_all)); + inline CNode::CNode(const TInterfaceAccessPtr& rptrNode) { m_pNodeInfo = rptrNode.GetInterface(); @@ -461,6 +522,16 @@ namespace sdv::toml return m_pNodeInfo ? m_pNodeInfo->GetType() : ENodeType::node_invalid; } + inline uint32_t CNode::GetIndex() const + { + return m_pNodeInfo ? m_pNodeInfo->GetIndex() : npos; + } + + inline bool CNode::IsInline() const + { + return m_pNodeInfo ? m_pNodeInfo->IsInline() : true; + } + inline std::string CNode::GetComment() const { if (!m_pNodeInfo) return {}; @@ -527,7 +598,9 @@ namespace sdv::toml { INodeUpdate* pNodeUpdate = m_ptrNode.GetInterface(); if (!pNodeUpdate) return false; - return pNodeUpdate->DeleteNode(); + bool bRet = pNodeUpdate->DeleteNode(); + Clear(); // Not valid any more. + return bRet; } inline void CNode::Clear() @@ -536,6 +609,11 @@ namespace sdv::toml m_pNodeInfo = nullptr; } + inline void CNode::AutomaticFormat(bool bRemoveComments) + { + if (m_pNodeInfo) m_pNodeInfo->AutomaticFormat(bRemoveComments); + } + inline sdv::u8string CNode::GetTOML() const { return m_pNodeInfo ? m_pNodeInfo->GetTOML() : sdv::u8string(); @@ -605,76 +683,86 @@ namespace sdv::toml return m_pCollection ? CNode(m_pCollection->GetNodeDirect(rssNode)) : CNode(); } - inline CNode CNodeCollection::InsertValue(size_t nIndex, const std::string& rssName, const sdv::any_t& ranyValue) + inline std::string CNodeCollection::GetNodeNameByIndex(size_t nIndex) const + { + if (!m_pCollection) return {}; + if (nIndex >= m_pCollection->GetCount()) return {}; + if (GetType() == ENodeType::node_array) return "[" + std::to_string(nIndex) + "]"; + TInterfaceAccessPtr ptrNode = m_pCollection->GetNode(static_cast(nIndex)); + const auto* pAccess = ptrNode.GetInterface(); + if (!pAccess) return {}; + return pAccess->GetName(); + } + + inline CNode CNodeCollection::InsertValue(const std::string& rssInsertBefore, const std::string& rssName, + const sdv::any_t& ranyValue) { INodeCollectionInsert* pInsert = m_ptrNode.GetInterface(); if (!pInsert) return {}; - return CNode(pInsert->InsertValue(static_cast(nIndex), rssName, ranyValue)); + return CNode(pInsert->InsertValue(rssInsertBefore, rssName, ranyValue)); } inline CNode CNodeCollection::AddValue(const std::string& rssName, const sdv::any_t& ranyValue) { INodeCollectionInsert* pInsert = m_ptrNode.GetInterface(); if (!pInsert) return {}; - return CNode(pInsert->InsertValue(npos, rssName, ranyValue)); + return CNode(pInsert->InsertValue("", rssName, ranyValue)); } - inline CNodeCollection CNodeCollection::InsertArray(size_t nIndex, const std::string& rssName) + inline CNodeCollection CNodeCollection::InsertArray(const std::string& rssInsertBefore, const std::string& rssName) { INodeCollectionInsert* pInsert = m_ptrNode.GetInterface(); if (!pInsert) return {}; - return CNodeCollection(pInsert->InsertArray(static_cast(nIndex), rssName)); + return CNodeCollection(pInsert->InsertArray(rssInsertBefore, rssName)); } inline CNodeCollection CNodeCollection::AddArray(const std::string& rssName) { INodeCollectionInsert* pInsert = m_ptrNode.GetInterface(); if (!pInsert) return {}; - return CNodeCollection(pInsert->InsertArray(npos, rssName)); + return CNodeCollection(pInsert->InsertArray("", rssName)); } - inline CNodeCollection CNodeCollection::InsertTable(size_t nIndex, const std::string& rssName, bool bFavorInline /*= false*/) + inline CNodeCollection CNodeCollection::InsertTable(const std::string& rssInsertBefore, const std::string& rssName, + bool bFavorInline /*= false*/) { INodeCollectionInsert* pInsert = m_ptrNode.GetInterface(); if (!pInsert) return {}; - return CNodeCollection(pInsert->InsertTable(static_cast(nIndex), rssName, - bFavorInline ? INodeCollectionInsert::EInsertPreference::prefer_inline : - INodeCollectionInsert::EInsertPreference::prefer_standard)); + return CNodeCollection(pInsert->InsertTable(rssInsertBefore, rssName, bFavorInline ? EInsertPreference::prefer_inline : + EInsertPreference::prefer_standard)); } inline CNodeCollection CNodeCollection::AddTable(const std::string& rssName, bool bFavorInline /*= false*/) { INodeCollectionInsert* pInsert = m_ptrNode.GetInterface(); if (!pInsert) return {}; - return CNodeCollection(pInsert->InsertTable(npos, rssName, - bFavorInline ? INodeCollectionInsert::EInsertPreference::prefer_inline : - INodeCollectionInsert::EInsertPreference::prefer_standard)); + return CNodeCollection(pInsert->InsertTable("", rssName, bFavorInline ? EInsertPreference::prefer_inline : + EInsertPreference::prefer_standard)); } - inline CNodeCollection CNodeCollection::InsertTableArray(size_t nIndex, const std::string& rssName, + inline CNodeCollection CNodeCollection::InsertTableArray(const std::string& rssInsertBefore, const std::string& rssName, bool bFavorInline /*= false*/) { INodeCollectionInsert* pInsert = m_ptrNode.GetInterface(); if (!pInsert) return {}; - return CNodeCollection(pInsert->InsertTableArray(static_cast(nIndex), rssName, - bFavorInline ? INodeCollectionInsert::EInsertPreference::prefer_inline : - INodeCollectionInsert::EInsertPreference::prefer_standard)); + return CNodeCollection(pInsert->InsertTableArray(rssInsertBefore, rssName, bFavorInline ? EInsertPreference::prefer_inline : + EInsertPreference::prefer_standard)); } inline CNodeCollection CNodeCollection::AddTableArray(const std::string& rssName, bool bFavorInline /*= false*/) { INodeCollectionInsert* pInsert = m_ptrNode.GetInterface(); if (!pInsert) return {}; - return CNodeCollection(pInsert->InsertTableArray(npos, rssName, - bFavorInline ? INodeCollectionInsert::EInsertPreference::prefer_inline : - INodeCollectionInsert::EInsertPreference::prefer_standard)); + return CNodeCollection(pInsert->InsertTableArray("", rssName, bFavorInline ? EInsertPreference::prefer_inline : + EInsertPreference::prefer_standard)); } - inline int CNodeCollection::InsertTOML(size_t nIndex, const std::string& rssTOML, bool bAllowPartial /*= false*/) + inline int CNodeCollection::InsertTOML(const std::string& rssInsertBefore, const std::string& rssTOML, + bool bAllowPartial /*= false*/) { INodeCollectionInsert* pInsert = m_ptrNode.GetInterface(); if (!pInsert) return 0; - INodeCollectionInsert::EInsertResult eRet = pInsert->InsertTOML(static_cast(nIndex), rssTOML, !bAllowPartial); + INodeCollectionInsert::EInsertResult eRet = pInsert->InsertTOML(rssInsertBefore, rssTOML, !bAllowPartial); switch (eRet) { case INodeCollectionInsert::EInsertResult::insert_success: @@ -691,7 +779,7 @@ namespace sdv::toml { INodeCollectionInsert* pInsert = m_ptrNode.GetInterface(); if (!pInsert) return 0; - INodeCollectionInsert::EInsertResult eRet = pInsert->InsertTOML(npos, rssTOML, !bAllowPartial); + INodeCollectionInsert::EInsertResult eRet = pInsert->InsertTOML("", rssTOML, !bAllowPartial); switch (eRet) { case INodeCollectionInsert::EInsertResult::insert_success: @@ -746,6 +834,59 @@ namespace sdv::toml m_ptrParserUtil.Clear(); } + namespace internal + { + inline ECompareResult CompareNodes(CNode& rnode1, CNode& rnode2, + uint32_t uiCompareFlags /*= static_cast(ECompareFlags::compare_ignore_all)*/) + { + if (!rnode1 || !rnode2) return ECompareResult::compare_error; + + // Format the nodes when ignore whitespace and/or comments + bool bIgnoreComments = uiCompareFlags & static_cast(ECompareFlags::compare_ignore_comments); + bool bIgnoreWhitespace = bIgnoreComments || + (uiCompareFlags & static_cast(ECompareFlags::compare_ignore_whitespace)); + if (bIgnoreWhitespace) + { + INodeInfo* pNodeInfo1 = rnode1.GetInterface().GetInterface(); + if (!pNodeInfo1) return ECompareResult::compare_error; + pNodeInfo1->AutomaticFormat(bIgnoreComments); + INodeInfo* pNodeInfo2 = rnode2.GetInterface().GetInterface(); + if (!pNodeInfo2) return ECompareResult::compare_error; + pNodeInfo2->AutomaticFormat(bIgnoreComments); + } + + // Convert to standard if ignoring inline + INodeCollectionConvert* pConvert1 = rnode1.GetInterface().GetInterface(); + INodeCollectionConvert* pConvert2 = rnode2.GetInterface().GetInterface(); + if (pConvert1 && pConvert2 && uiCompareFlags & static_cast(ECompareFlags::compare_ignore_inline)) + { + // Making inline nodes as standard, might only change the upper node. + // Making standard nodes inline, will have all child nodes be made inline as well, because inline nodes can only + // have inline nodes. + pConvert1->MakeInline(); + pConvert2->MakeInline(); + } + + // Generate the TOMLs and compare + return rnode1.GetTOML() == rnode2.GetTOML() ? ECompareResult::compare_identical : ECompareResult::compare_different; + } + } // namespace internal + + inline ECompareResult Compare(const std::string& rssToml1, const std::string& rssToml2, + uint32_t uiCompareFlags /*= static_cast(ECompareFlags::compare_ignore_all)*/) + { + try + { + CTOMLParser parser1(rssToml1); + CTOMLParser parser2(rssToml2); + return internal::CompareNodes(parser1, parser2, uiCompareFlags); + } + catch (const sdv::toml::XTOMLParseException&) + { + return ECompareResult::compare_error; + } + } + } -#endif // !defined SDV_CONFIG_H \ No newline at end of file +#endif // !defined SDV_TOML_H \ No newline at end of file diff --git a/funding_logos/CHIPS_logo_RGB_full_colour.png b/funding_logos/CHIPS_logo_RGB_full_colour.png new file mode 100644 index 0000000000000000000000000000000000000000..3b76409729afd7cc138df3d8e696978449ecfc6c GIT binary patch literal 161988 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 z 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

hnn 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 literal 0 HcmV?d00001 diff --git a/funding_logos/EN_Co-fundedbytheEU_RGB_POS.png b/funding_logos/EN_Co-fundedbytheEU_RGB_POS.png new file mode 100644 index 0000000000000000000000000000000000000000..2a494fb227060f0ba3128552fd4bcda6e4ed7a81 GIT binary patch literal 69534 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 z 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 uR + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/global/dbcparser/dbcparser.cpp b/global/dbcparser/dbcparser.cpp index 80410b6..f2ccf66 100644 --- a/global/dbcparser/dbcparser.cpp +++ b/global/dbcparser/dbcparser.cpp @@ -451,7 +451,7 @@ namespace dbc if (!bNoDefaultDef) { // Parse the standard attribute definitions - CDbcSource source(std::string(R"code( + CDbcSource source(std::string(R"dbc( BA_DEF_ BO_ "VFrameFormat" ENUM "StandardCAN","ExtendedCAN","StandardFD","ExtendedFD"; BA_DEF_ BO_ "GenMsgSendType" ENUM "cyclic","triggered","cyclicIfActive","cyclicAndTriggered","cyclicIfActiveAndTriggered","none"; BA_DEF_ BO_ "GenMsgCycleTime" INT 0 10000; @@ -472,7 +472,7 @@ BA_DEF_DEF_ "GenSigDelayTime" 0; BA_DEF_DEF_ "GenSigCycleTime" 0; BA_DEF_DEF_ "GenSigStartDelayTime" 0; BA_DEF_DEF_ "GenSigStartValue" 0; - )code")); + )dbc")); Parse(source); } } diff --git a/global/debug_log.h b/global/debug_log.h index 24cfc50..549b80f 100644 --- a/global/debug_log.h +++ b/global/debug_log.h @@ -176,9 +176,9 @@ namespace debug */ struct SLogMsg { - std::thread::id id; ///< Thread ID - size_t nDepth; ///< Depth within the calls - std::string ssMsg; ///< Message to log + std::thread::id id; ///< Thread ID + size_t nDepth; ///< Depth within the calls + std::string ssMsg; ///< Message to log }; /** @@ -271,7 +271,7 @@ namespace debug std::filesystem::path m_pathLogFile; ///< Path to the log file. #if DECOUPLED_DEBUG_LOG != 0 - std::mutex m_mtxLogger; ///< Protect against multiple log entries at the same time. + std::mutex m_mtxLogger; ///< Protect against multiple log entries at the same time. std::thread m_threadLogger; ///< Logger thread std::atomic_bool m_bShutdown = false; ///< When set, terminate the logging thread std::queue m_queueLogger; ///< Queue with messages to log. diff --git a/global/scheduler/scheduler.h b/global/scheduler/scheduler.h index 28de486..c702dcd 100644 --- a/global/scheduler/scheduler.h +++ b/global/scheduler/scheduler.h @@ -11,8 +11,8 @@ * Erik Verhoeven - initial API and implementation ********************************************************************************/ -#ifndef THREAD_POOL_H -#define THREAD_POOL_H +#ifndef SCHEDULER_H +#define SCHEDULER_H #include #include @@ -27,33 +27,38 @@ #include "../flags.h" /** - * @brief Job scheduler that uses a dynamic threadpool to schedule the task. + * @brief Schedule flags to influence the scheduling. */ +enum class EScheduleFlags +{ + normal = 0x0, ///< If thread level threshold has been reached, queue the call at the end of the queue. + priority = 0x1, ///< If thread level threshold has been reached, insert the call at the begin of the queue. + no_queue = 0x2, ///< If thread level threshold has been reached, fail the schedule call. +}; + +/** + * @brief Task scheduler that uses a dynamic threadpool to schedule the task. + * @tparam TThread Type of thread class to use for scheduling; must be derived from std::thread. + * @tparam nMinIdle The amount of idle threads that should stay present if there is nothing to process at the moment. + * @tparam nMaxBusy The maximum amount of threads that is used for processing. + */ +template class CTaskScheduler { public: + // The thread class must be derived from std::thread. + static_assert(std::is_base_of_v); + /** * @brief Constructor - * @param[in] nMinIdle The amount of idle threads that should stay present if there is nothing to process at the moment. - * @param[in] nMaxBusy The maximum amount of threads that is used for processing. */ - CTaskScheduler(size_t nMinIdle = 4, size_t nMaxBusy = 32); + CTaskScheduler(); /** * @brief Destructor */ ~CTaskScheduler(); - /** - * @brief Schedule flags to influence the scheduling. - */ - enum class EScheduleFlags - { - normal = 0x0, ///< If thread level threshold has been reached, queue the call at the end of the queue. - priority = 0x1, ///< If thread level threshold has been reached, insert the call at the begin of the queue. - no_queue = 0x2, ///< If thread level threshold has been reached, fail the schedule call. - }; - /** * @brief Schedule the asynchronous execution of the task. * @details If an idle thread is available, the thread will execute the task. If no thread is available and the maximum thread @@ -125,7 +130,7 @@ private: */ void ThreadFunc(); - std::thread m_thread; ///< The thread that executes the tasks. + TThread m_thread; ///< The thread that executes the tasks. std::atomic_bool m_bShutdown = false; ///< Set when the thread should terminate. std::atomic_bool m_bStarted = false; ///< Set when the thread has started. std::function m_fnTask; ///< The task to execute (will be updated with new tasks before execution). @@ -143,15 +148,18 @@ private: * @param[in] itThread The thread to use for the execution. * @param[in] fnTask The task to execute. */ - void Execute(std::list>::iterator itThread, std::function fnTask); + void Execute(typename std::list>::iterator itThread, std::function fnTask); size_t m_nMinIdle = 4; ///< The minimal required amount of idle threads size_t m_nMaxBusy = 32; ///< The maximum allowed amount of busy threads size_t m_nMaxThreads = 0; ///< The maximum amount threads at the same time. mutable std::mutex m_mtxQueueAccess; ///< Sync access to queue, list and double-ended-queue. - std::queue>::iterator> m_queueIdleThreads; ///< Idle thread queue. - std::list> m_lstThreads; ///< List with all threads. + std::queue>::iterator> m_queueIdleThreads; ///< Idle thread queue. + std::list> m_lstThreads; ///< List with all threads. std::deque> m_dequeTasks; ///< Double ended task queue. }; -#endif // !defined THREAD_POOL_H \ No newline at end of file +// Include the implementation +#include "scheduler.inl" + +#endif // !defined SCHEDULER_H \ No newline at end of file diff --git a/global/scheduler/scheduler.cpp b/global/scheduler/scheduler.inl similarity index 74% rename from global/scheduler/scheduler.cpp rename to global/scheduler/scheduler.inl index 274d688..3b11a89 100644 --- a/global/scheduler/scheduler.cpp +++ b/global/scheduler/scheduler.inl @@ -11,10 +11,17 @@ * Erik Verhoeven - initial API and implementation ********************************************************************************/ +#ifndef SCHEDULER_H #include "scheduler.h" +#endif + +#ifndef SCHEDULER_INL +#define SCHEDULER_INL + #include -CTaskScheduler::CTaskScheduler(size_t nMinIdle /*= 4*/, size_t nMaxBusy /*= 32*/) : +template +inline CTaskScheduler::CTaskScheduler() : m_nMinIdle(nMinIdle), m_nMaxBusy(nMaxBusy) { // Check min/max values @@ -30,17 +37,20 @@ CTaskScheduler::CTaskScheduler(size_t nMinIdle /*= 4*/, size_t nMaxBusy /*= 32*/ m_nMaxThreads = m_lstThreads.size(); } -CTaskScheduler::~CTaskScheduler() +template +inline CTaskScheduler::~CTaskScheduler() { WaitForExecution(); } -bool CTaskScheduler::Schedule(std::function fnTask, hlpr::flags flags /*= 0*/) +template +inline bool CTaskScheduler::Schedule(std::function fnTask, + hlpr::flags flags /*= 0*/) { std::unique_lock lock(m_mtxQueueAccess); // Get a thread - either from the idle queue or new. - std::list>::iterator itThread = m_lstThreads.end(); + typename std::list>::iterator itThread = m_lstThreads.end(); if (m_queueIdleThreads.empty()) { // No thread is available. Allowed to make one? @@ -77,7 +87,8 @@ bool CTaskScheduler::Schedule(std::function fnTask, hlpr::flags +inline void CTaskScheduler::WaitForExecution() { std::unique_lock lock(m_mtxQueueAccess); @@ -111,31 +122,37 @@ void CTaskScheduler::WaitForExecution() m_nMinIdle = nMinIdleTemp; } -size_t CTaskScheduler::GetThreadCount() const +template +inline size_t CTaskScheduler::GetThreadCount() const { std::unique_lock lock(m_mtxQueueAccess); return m_lstThreads.size(); } -size_t CTaskScheduler::GetMaxThreadCount() const +template +inline size_t CTaskScheduler::GetMaxThreadCount() const { std::unique_lock lock(m_mtxQueueAccess); return m_nMaxThreads; } -size_t CTaskScheduler::GetBusyThreadCount() const +template +inline size_t CTaskScheduler::GetBusyThreadCount() const { std::unique_lock lock(m_mtxQueueAccess); return m_lstThreads.size() - m_queueIdleThreads.size(); } -size_t CTaskScheduler::GetIdleThreadCount() const +template +inline size_t CTaskScheduler::GetIdleThreadCount() const { std::unique_lock lock(m_mtxQueueAccess); return m_queueIdleThreads.size(); } -void CTaskScheduler::Execute(std::list>::iterator itThread, std::function fnTask) +template +inline void CTaskScheduler::Execute(typename std::list>::iterator itThread, + std::function fnTask) { std::shared_ptr& rptrThread = *itThread; @@ -176,16 +193,18 @@ void CTaskScheduler::Execute(std::list>::iterator itThr }); } -CTaskScheduler::CThread::CThread() +template +inline CTaskScheduler::CThread::CThread() { // Wait for the start std::unique_lock lockStart(m_mtxSyncStart); - m_thread = std::thread(&CThread::ThreadFunc, this); + m_thread = TThread(&CThread::ThreadFunc, this); while (!m_bStarted) m_cvStarted.wait_for(lockStart, std::chrono::milliseconds(10)); } -CTaskScheduler::CThread::~CThread() +template +inline CTaskScheduler::CThread::~CThread() { // Lock to prevent execution still to take place. Then shutdown. std::unique_lock lock(m_mtxSyncExecute); @@ -196,7 +215,8 @@ CTaskScheduler::CThread::~CThread() m_thread.join(); } -void CTaskScheduler::CThread::Execute(std::function fnTask) +template +inline void CTaskScheduler::CThread::Execute(std::function fnTask) { // Assign the task and notify for execution. std::unique_lock lock(m_mtxSyncExecute); @@ -205,7 +225,8 @@ void CTaskScheduler::CThread::Execute(std::function fnTask) lock.unlock(); } -void CTaskScheduler::CThread::ThreadFunc() +template +inline void CTaskScheduler::CThread::ThreadFunc() { // Thread has started (needed, since the condition variable doesn't keep its state). m_bStarted = true; @@ -232,3 +253,5 @@ void CTaskScheduler::CThread::ThreadFunc() m_fnTask = {}; } } + +#endif // !defined SCHEDULER_INL \ No newline at end of file diff --git a/global/tracefifo/trace_fifo.h b/global/tracefifo/trace_fifo.h index f664a06..5a4a4b8 100644 --- a/global/tracefifo/trace_fifo.h +++ b/global/tracefifo/trace_fifo.h @@ -20,6 +20,8 @@ #include #include #include +#include +#include /** * @brief trace fifo open flags diff --git a/global/unique_id.h b/global/unique_id.h new file mode 100644 index 0000000..ea80a33 --- /dev/null +++ b/global/unique_id.h @@ -0,0 +1,114 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#ifndef UNIQUE_ID_H +#define UNIQUE_ID_H + +#include +#include +#include +#include + +/** + * @brief Create a unique ID using the random number generator. + * @tparam TType Type of the variable that represents the ID. + * @tparam nDepth The depth of the ID generation in bits. + */ +template +class CUniqueID +{ +public: + static_assert(nDepth <= sizeof(TType) * 8); + static_assert(nDepth > 0); + + /** + * @brief Constructor + */ + CUniqueID() + { + std::srand(static_cast(std::time(nullptr))); + } + + /** + * @brief Generate a unique ID. + * @return The optional result with the ID if successful or 0 when not successful. + */ + TType Generate() + { + // Create a number with the supplied bit amount. + auto fnCreate = [](size_t nMaxDepth) + { + TType tNumber{}; + + // Create a new number + size_t nBitsFilled = 0; + while (nBitsFilled < nMaxDepth) + { + size_t nBitsToShiftIn = std::min(nMaxDepth - nBitsFilled, m_nRandMaxBits); + tNumber = (tNumber << nBitsToShiftIn) | static_cast(static_cast(std::rand()) & ((1u << nBitsToShiftIn) - 1)); + nBitsFilled += nBitsToShiftIn; + } + return tNumber; + }; + + // Treat a depth of 48 bits and more separately + if constexpr (nDepth >= 48) + { + // Combination of the time in seconds (22 bits only - can hold 48 days). + return (static_cast(std::time(nullptr)) << (nDepth - 22)) | fnCreate(nDepth - 22); + } + else + { + // Maximum iterations is 10000 for less than 24 bits and otherwise unlimited (value 0) + size_t nMaxIterations = nDepth < 24 ? 10000u : 0u; + + // Generate + size_t nCnt = 0; + while (!nMaxIterations || (++nCnt < nMaxIterations)) + { + TType tID = fnCreate(nDepth); + + // Check whether not 0 and not generated already + if (tID && (m_setUsedNumbers.find(tID) == m_setUsedNumbers.end())) + { + m_setUsedNumbers.insert(tID); + return tID; + } + } + + // No ID found + return TType{}; + } + } + +private: + /** + * @brief Recursive constexpr function to find the number of bits needed to hold the number. + * @param uiNumber The number to check for. + * @return The amount of bits needed to hold the number. + */ + static constexpr size_t GetBitWidth(uint64_t uiNumber) + { + return (!uiNumber) ? 0u : 1u + GetBitWidth(uiNumber >> 1); + } + + /// The bits needed to hold the random number + static constexpr size_t m_nRandMaxBits = GetBitWidth(static_cast(RAND_MAX)); + + // Static assertion to verify at compile-time + static_assert(m_nRandMaxBits > 0, "RAND_MAX bits must be greater than 0"); + + std::set m_setUsedNumbers; ///< Numbers generated previously already (numbers < 48 bits only). +}; + +#endif // !defined UNIQUE_ID_H diff --git a/sdv_executables/error_msg.h b/sdv_executables/error_msg.h index e5f4667..99fb89d 100644 --- a/sdv_executables/error_msg.h +++ b/sdv_executables/error_msg.h @@ -60,12 +60,22 @@ MAKE_ERROR_MSG(-134, CONFIG_SERVICE_ACCESS_ERROR, "Configuration service is not MAKE_ERROR_MSG(-135, COMMUNICATION_CONTROL_SERVICE_ACCESS_ERROR, "Communication control service is not accessible.", "The communication control service service could not be accessed.") MAKE_ERROR_MSG(-136, APP_CONTROL_SERVICE_ACCESS_ERROR, "Application control service is not accessible.", "The application control service could not be accessed.") MAKE_ERROR_MSG(-170, CANNOT_FIND_OBJECT, "The object cannot be found.", "A search for an object with supplied name was not successful.") +MAKE_ERROR_MSG(-180, CANNOT_LOAD_SETTINGS, "The system settings cannot be loaded.", "For a specific instance, the system settings cannot be loaded.") +MAKE_ERROR_MSG(-180, CANNOT_SAVE_SETTINGS, "The system settings cannot be saved.", "For a specific instance, the system settings cannot be saved.") +MAKE_ERROR_MSG(-181, PROCESS_LISTENER_CONFIG_ERROR, "The listener configuration contains errors.", "The listener configuration contains errors and cannot be processed.") +MAKE_ERROR_MSG(-182, PROCESS_CONNECTION_CONFIG_ERROR, "The connection configuration contains errors.", "The connection configuration contains errors and cannot be processed.") +MAKE_ERROR_MSG(-183, ADD_LISTENER_ERROR, "Failed to add a listener to the system settings.", "The listener configuration could not be added to the system settings. This might be caused by a duplicate listener configuration.") +MAKE_ERROR_MSG(-184, REMOVE_LISTENER_ERROR, "Failed to remove a listener from the system settings.", "The listener configuration could not be removed from the system settings. This might be caused by a missing listener configuration.") +MAKE_ERROR_MSG(-185, SHOW_LISTENER_ERROR, "Failed to request the listener configuration from the system settings.", "The listener configuration could not be requested. This might be caused by a missing listener configuration.") +MAKE_ERROR_MSG(-186, ADD_CONNECTION_ERROR, "Failed to add a connection to the system settings.", "The connection configuration could not be added to the system settings. This might be caused by a duplicate connection configuration.") +MAKE_ERROR_MSG(-187, REMOVE_CONNECTION_ERROR, "Failed to remove a connection from the system settings.", "The connection configuration could not be removed from the system settings. This might be caused by a missing connection configuration.") +MAKE_ERROR_MSG(-188, SHOW_CONNECTION_ERROR, "Failed to request the connection configuration from the system settings.", "The connection configuration could not be requested. This might be caused by a missing connection configuration.") ////////// LOCAL APP CONTROL ERROR CODES //////////// MAKE_ERROR_MSG(-204, APP_CONTROL_STARTUP_ERROR, "Cannot start application control.", "Application control start was initiated, but returned an error.") -MAKE_ERROR_MSG(-205, APP_CONTROL_START_LOOP_FAIELD, "Starting the running loop failed.", "Failed to enter the processing loop for a server application.") +MAKE_ERROR_MSG(-205, APP_CONTROL_START_LOOP_FAILED, "Starting the running loop failed.", "Failed to enter the processing loop for a server application.") MAKE_ERROR_MSG(-206, APP_CONTROL_DUPLICATE_INSTANCE, "Another process with specified instance is already running.", "Another core process initiated the application control with the same instance. Only one core process per instance is allowed.") MAKE_ERROR_MSG(-210, APP_CONTROL_INVALID_ISOLATION_CONFIG, "An invalid isolation configuration was supplied.", "The application control configuration section contains an invalid isolation configuration.") MAKE_ERROR_MSG(-218, SETTING_FILE_VERSION_INVALID, "Settings file has an invalid version.", "Settings file contains an invalid/incompatible version number.") diff --git a/sdv_executables/sdv_control/CMakeLists.txt b/sdv_executables/sdv_control/CMakeLists.txt index 9be7b01..7a7e419 100644 --- a/sdv_executables/sdv_control/CMakeLists.txt +++ b/sdv_executables/sdv_control/CMakeLists.txt @@ -25,7 +25,7 @@ add_executable(sdv_control "startup_shutdown.h" "startup_shutdown.cpp" "context.h" - "print_table.h" "start_stop_service.cpp" "start_stop_service.h" "installation.h" "installation.cpp") + "print_table.h" "start_stop_service.cpp" "start_stop_service.h" "installation.h" "installation.cpp" "connection_config.h" "connection_config.cpp") target_link_libraries(sdv_control ${CMAKE_DL_LIBS}) diff --git a/sdv_executables/sdv_control/connection_config.cpp b/sdv_executables/sdv_control/connection_config.cpp new file mode 100644 index 0000000..e0cada3 --- /dev/null +++ b/sdv_executables/sdv_control/connection_config.cpp @@ -0,0 +1,471 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#include "connection_config.h" +#include "../../global/cmdlnparser/cmdlnparser.h" +#include +#include +#include "../error_msg.h" +#include + +void ListenerHelp(const SContext& rsContext) +{ + // First argument should be "LISTENER" + if (rsContext.seqCmdLine.size() < 1) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: missing listener command..." << std::endl; + return; + } + if (iequals(rsContext.seqCmdLine[0], "LISTENER")) + { + CCommandLine::PrintHelpText(std::cout, + "Usage: sdv_control LISTENER ADD [options...]\n" + " sdv_control LISTENER REMOVE [options...]\n" + " sdv_control LISTENER SHOW [options...]\n\n" + "Configure the system to add (or update) or remove a listener. The command doesn't check for valid parameters. A " + "server restart is required for the new listener settings to take effect.\n\n" + "A list of actively configured listeners can be requested by executing \"sdv_control LIST LISTENERS\"\n\n" + "Note: if not explicitly added, a default listener will added to the list for the communication between isolated " + "applications and the core system.\n\n"); + return; + } + if (!rsContext.bSilent) + std::cerr << "ERROR: invalid listener command..." << std::endl; +} + +void ConnectionHelp(const SContext& rsContext) +{ + // First argument should be "CONNECTION" + if (rsContext.seqCmdLine.size() < 1) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: missing connection command..." << std::endl; + return; + } + if (iequals(rsContext.seqCmdLine[0], "CONNECTION")) + { + CCommandLine::PrintHelpText(std::cout, + "Usage: sdv_control CONNECTION ADD [options...]\n" + " sdv_control CONNECTION REMOVE [options...]\n" + " sdv_control CONNECTION SHOW [options...]\n\n" + "Configure the system to add (or update) or remove a connection. The command doesn't check for valid parameters. A " + "server restart is required for the new connection settings to take effect.\n\n" + "A list of actively configured connections can be requested by executing \"sdv_control LIST CONNECTIONS\"\n\n" + "Note: if not explicitly added, a default connetion will be added to the list for the communication between external " + "applications and the core system.\n\n" + "Options:\n" + " --insert_before With ADD, Insert the connection before an existing connection with the supplied name.\n\n"); + return; + } + if (!rsContext.bSilent) + std::cerr << "ERROR: invalid connection command..." << std::endl; +} + +int ConfigureListener(const SContext& rsContext) +{ + // First argument should be "CONNECTION" + if (rsContext.seqCmdLine.size() < 2) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << CMDLN_ARG_ERR_MSG << " Missing listener command... " << std::endl; + return CMDLN_ARG_ERR; + } + if (!iequals(rsContext.seqCmdLine[0], "LISTENER")) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << CMDLN_ARG_ERR_MSG << " Invalid command: " << rsContext.seqCmdLine[0] << std::endl; + return CMDLN_ARG_ERR; + } + + // Which list command is requested + enum class ECommand { unknown, add, remove, show } eCommand = ECommand::unknown; + if (iequals(rsContext.seqCmdLine[1], "ADD")) eCommand = ECommand::add; + else if (iequals(rsContext.seqCmdLine[1], "REMOVE")) eCommand = ECommand::remove; + else if (iequals(rsContext.seqCmdLine[1], "SHOW")) eCommand = ECommand::show; + else + { + if (!rsContext.bSilent) + { + std::cerr << "ERROR: " << CMDLN_ARG_ERR_MSG << " Invalid listener command." << std::endl; + ListenerHelp(rsContext); + } + return CMDLN_ARG_ERR; + } + + if (rsContext.seqCmdLine.size() < 3) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << CMDLN_ARG_ERR_MSG << " Missing listener name... " << std::endl; + return CMDLN_ARG_ERR; + } + + // Get the name + std::string ssListener = rsContext.seqCmdLine[2]; + + switch (eCommand) + { + case ECommand::remove: + return RemoveListener(ssListener, rsContext); + case ECommand::show: + return ShowListenerConfig(ssListener, rsContext); + case ECommand::add: + default: + break; + } + + // For add, check for provider and parameters + if (rsContext.seqCmdLine.size() < 4) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << CMDLN_ARG_ERR_MSG << " Missing listener provider... " << std::endl; + return CMDLN_ARG_ERR; + } + std::string ssProvider = rsContext.seqCmdLine[3]; + + sdv::toml::CTOMLParser parser; + parser.AddValue("Provider.Name", ssProvider); + + // Check for parameters + // Parameters have the format "param=value". It is possible that parameters have additional sub-tables. Then the format is: + // "table.param=value". + sdv::toml::CNodeCollection ipc_channel_table = parser.AddTable("IpcChannel"); + for (size_t nIndex = 4; nIndex < rsContext.seqCmdLine.size(); nIndex++) + { + std::string ssRawParam = rsContext.seqCmdLine[nIndex]; + + // Search for the equal sign + size_t nEqualPos = ssRawParam.find_first_of('='); + std::string ssParamName = ssRawParam.substr(0, nEqualPos); + while (!ssParamName.empty() && std::isspace(ssParamName.back())) + ssParamName.pop_back(); + if (ssParamName.empty() || nEqualPos == std::string::npos) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << CMDLN_ARG_ERR_MSG << " Invalid listener parameter: " << ssRawParam << std::endl; + return CMDLN_ARG_ERR; + } + sdv::any_t anyValue; + size_t nValuePos = nEqualPos + 1; + while (nValuePos < ssRawParam.size() && std::isspace(ssRawParam[nValuePos])) + nValuePos++; + if (nValuePos < ssRawParam.size()) + { + std::string ssValue = ssRawParam.substr(nValuePos); + if (!ssValue.empty() && ssValue.find_first_not_of("-0123456789") == std::string::npos) + { + int64_t iValue = 0; + if (std::from_chars(ssValue.data(), ssValue.data() + ssValue.size(), iValue).ec == std::errc()) + anyValue = iValue; + else + anyValue = ssValue; + } + else + anyValue = ssValue; + + } + ipc_channel_table.AddValue(ssParamName, anyValue); + } + + return AddListener(ssListener, parser.GetTOML(), rsContext); +} + +int AddListener(const std::string& rssListener, const std::string& rssConfig, const SContext& rsContext) +{ + // Load the settings. + auto ptrAppService = sdv::core::GetObject("AppSettingsService"); + sdv::app::IAppSettingsPersist* pAppSettingsPersist = ptrAppService.GetInterface(); + sdv::app::IAppConnections* pAppConnections = ptrAppService.GetInterface(); + if (!pAppSettingsPersist || !pAppConnections || !pAppSettingsPersist->LoadSettings()) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << CANNOT_LOAD_SETTINGS_MSG << std::endl; + return CANNOT_LOAD_SETTINGS; + } + + // Add the listener + if (!pAppConnections->AddListenerConfig(rssListener, rssConfig)) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << ADD_LISTENER_ERROR_MSG << std::endl; + return ADD_LISTENER_ERROR; + } + + // Save the settings. + if (!pAppSettingsPersist->SaveSettings()) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << CANNOT_SAVE_SETTINGS_MSG << std::endl; + return CANNOT_SAVE_SETTINGS; + } + + return NO_ERROR; +} + +int RemoveListener(const std::string& rssListener, const SContext& rsContext) +{ + // Load the settings. + auto ptrAppService = sdv::core::GetObject("AppSettingsService"); + sdv::app::IAppSettingsPersist* pAppSettingsPersist = ptrAppService.GetInterface(); + sdv::app::IAppConnections* pAppConnections = ptrAppService.GetInterface(); + if (!pAppSettingsPersist || !pAppConnections || !pAppSettingsPersist->LoadSettings()) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << CANNOT_LOAD_SETTINGS_MSG << std::endl; + return CANNOT_LOAD_SETTINGS; + } + + // Remove the listener + if (!pAppConnections->RemoveListenerConfig(rssListener)) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << REMOVE_LISTENER_ERROR_MSG << std::endl; + return REMOVE_LISTENER_ERROR; + } + + // Save the settings. + if (!pAppSettingsPersist->SaveSettings()) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << CANNOT_SAVE_SETTINGS_MSG << std::endl; + return CANNOT_SAVE_SETTINGS; + } + + return NO_ERROR; +} + +int ShowListenerConfig(const std::string& rssListener, const SContext& rsContext) +{ + // Load the settings. + auto ptrAppService = sdv::core::GetObject("AppSettingsService"); + sdv::app::IAppSettingsPersist* pAppSettingsPersist = ptrAppService.GetInterface(); + sdv::app::IAppConnections* pAppConnections = ptrAppService.GetInterface(); + if (!pAppSettingsPersist || !pAppConnections || !pAppSettingsPersist->LoadSettings()) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << CANNOT_LOAD_SETTINGS_MSG << std::endl; + return CANNOT_LOAD_SETTINGS; + } + + // Remove the listener + std::string ssConfig = pAppConnections->GetListenerConfig(rssListener); + if (ssConfig.empty()) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << SHOW_LISTENER_ERROR_MSG << std::endl; + return SHOW_LISTENER_ERROR; + } + + std::cout << ssConfig << std::endl; + + return NO_ERROR; +} + +int ConfigureConnection(const SContext& rsContext) +{ + // First argument should be "CONNECTION" + if (rsContext.seqCmdLine.size() < 2) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << CMDLN_ARG_ERR_MSG << " Missing connection command... " << std::endl; + return CMDLN_ARG_ERR; + } + if (!iequals(rsContext.seqCmdLine[0], "CONNECTION")) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << CMDLN_ARG_ERR_MSG << " Invalid command: " << rsContext.seqCmdLine[0] << std::endl; + return CMDLN_ARG_ERR; + } + + // Which list command is requested + enum class ECommand { unknown, add, remove, show } eCommand = ECommand::unknown; + if (iequals(rsContext.seqCmdLine[1], "ADD")) eCommand = ECommand::add; + else if (iequals(rsContext.seqCmdLine[1], "REMOVE")) eCommand = ECommand::remove; + else if (iequals(rsContext.seqCmdLine[1], "SHOW")) eCommand = ECommand::show; + else + { + if (!rsContext.bSilent) + { + std::cerr << "ERROR: " << CMDLN_ARG_ERR_MSG << " Invalid connection command." << std::endl; + ConnectionHelp(rsContext); + } + return CMDLN_ARG_ERR; + } + + if (rsContext.seqCmdLine.size() < 3) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << CMDLN_ARG_ERR_MSG << " Missing connection name... " << std::endl; + return CMDLN_ARG_ERR; + } + + // Get the name + std::string ssConnection = rsContext.seqCmdLine[2]; + + switch (eCommand) + { + case ECommand::remove: + return RemoveConnection(ssConnection, rsContext); + case ECommand::show: + return ShowConnectionConfig(ssConnection, rsContext); + case ECommand::add: + default: + break; + } + + // For add, check for provider and parameters + if (rsContext.seqCmdLine.size() < 4) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << CMDLN_ARG_ERR_MSG << " Missing connection provider... " << std::endl; + return CMDLN_ARG_ERR; + } + std::string ssProvider = rsContext.seqCmdLine[3]; + + sdv::toml::CTOMLParser parser; + parser.AddValue("Provider.Name", ssProvider); + + // Check for parameters + // Parameters have the format "param=value". It is possible that parameters have additional sub-tables. Then the format is: + // "table.param=value". + sdv::toml::CNodeCollection ipc_channel_table = parser.AddTable("IpcChannel"); + for (size_t nIndex = 4; nIndex < rsContext.seqCmdLine.size(); nIndex++) + { + std::string ssRawParam = rsContext.seqCmdLine[nIndex]; + + // Search for the equal sign + size_t nEqualPos = ssRawParam.find_first_of('='); + std::string ssParamName = ssRawParam.substr(0, nEqualPos); + while (!ssParamName.empty() && std::isspace(ssParamName.back())) + ssParamName.pop_back(); + if (ssParamName.empty() || nEqualPos == std::string::npos) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << CMDLN_ARG_ERR_MSG << " Invalid listener parameter: " << ssRawParam << std::endl; + return CMDLN_ARG_ERR; + } + sdv::any_t anyValue; + size_t nValuePos = nEqualPos + 1; + while (nValuePos < ssRawParam.size() && std::isspace(ssRawParam[nValuePos])) + nValuePos++; + if (nValuePos < ssRawParam.size()) + { + std::string ssValue = ssRawParam.substr(nValuePos); + if (!ssValue.empty() && ssValue.find_first_not_of("-0123456789") == std::string::npos) + { + int64_t iValue = 0; + if (std::from_chars(ssValue.data(), ssValue.data() + ssValue.size(), iValue).ec == std::errc()) + anyValue = iValue; + else + anyValue = ssValue; + } + else + anyValue = ssValue; + + } + ipc_channel_table.AddValue(ssParamName, anyValue); + } + + return AddConnection(ssConnection, parser.GetTOML(), rsContext); +} + +int AddConnection(const std::string& rssConnection, const std::string& rssConfig, const SContext& rsContext) +{ + // Load the settings. + auto ptrAppService = sdv::core::GetObject("AppSettingsService"); + sdv::app::IAppSettingsPersist* pAppSettingsPersist = ptrAppService.GetInterface(); + sdv::app::IAppConnections* pAppConnections = ptrAppService.GetInterface(); + if (!pAppSettingsPersist || !pAppConnections || !pAppSettingsPersist->LoadSettings()) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << CANNOT_LOAD_SETTINGS_MSG << std::endl; + return CANNOT_LOAD_SETTINGS; + } + + // Add the connection + if (!pAppConnections->AddConnectionConfig(rssConnection, rssConfig, rsContext.ssInsertBeforeConnection)) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << ADD_CONNECTION_ERROR_MSG << std::endl; + return ADD_CONNECTION_ERROR; + } + + // Save the settings. + if (!pAppSettingsPersist->SaveSettings()) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << CANNOT_SAVE_SETTINGS_MSG << std::endl; + return CANNOT_SAVE_SETTINGS; + } + + return NO_ERROR; +} + +int RemoveConnection(const std::string& rssConnection, const SContext& rsContext) +{ + // Load the settings. + auto ptrAppService = sdv::core::GetObject("AppSettingsService"); + sdv::app::IAppSettingsPersist* pAppSettingsPersist = ptrAppService.GetInterface(); + sdv::app::IAppConnections* pAppConnections = ptrAppService.GetInterface(); + if (!pAppSettingsPersist || !pAppConnections || !pAppSettingsPersist->LoadSettings()) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << CANNOT_LOAD_SETTINGS_MSG << std::endl; + return CANNOT_LOAD_SETTINGS; + } + + // Remove the connection + if (!pAppConnections->RemoveConnectionConfig(rssConnection)) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << REMOVE_CONNECTION_ERROR_MSG << std::endl; + return REMOVE_CONNECTION_ERROR; + } + + // Save the settings. + if (!pAppSettingsPersist->SaveSettings()) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << CANNOT_SAVE_SETTINGS_MSG << std::endl; + return CANNOT_SAVE_SETTINGS; + } + + return NO_ERROR; +} + +int ShowConnectionConfig(const std::string& rssConnection, const SContext& rsContext) +{ + // Load the settings. + auto ptrAppService = sdv::core::GetObject("AppSettingsService"); + sdv::app::IAppSettingsPersist* pAppSettingsPersist = ptrAppService.GetInterface(); + sdv::app::IAppConnections* pAppConnections = ptrAppService.GetInterface(); + if (!pAppSettingsPersist || !pAppConnections || !pAppSettingsPersist->LoadSettings()) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << CANNOT_LOAD_SETTINGS_MSG << std::endl; + return CANNOT_LOAD_SETTINGS; + } + + // Remove the connection + std::string ssConfig = pAppConnections->GetConnectionConfig(rssConnection); + if (ssConfig.empty()) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << SHOW_CONNECTION_ERROR_MSG << std::endl; + return SHOW_CONNECTION_ERROR; + } + + std::cout << ssConfig << std::endl; + + return NO_ERROR; +} diff --git a/sdv_executables/sdv_control/connection_config.h b/sdv_executables/sdv_control/connection_config.h new file mode 100644 index 0000000..5b2ceae --- /dev/null +++ b/sdv_executables/sdv_control/connection_config.h @@ -0,0 +1,98 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#ifndef CONNECTION_CONFIG_H +#define CONNECTION_CONFIG_H + +#include "context.h" +#include +#include + +/** + * @brief Help for configuring listeners. + * @param[in] rsContext Reference to the context. + */ +void ListenerHelp(const SContext& rsContext); + +/** + * @brief Help for configuring connections. + * @param[in] rsContext Reference to the context. + */ +void ConnectionHelp(const SContext& rsContext); + +/** + * @brief Configure the listener. + * @param[in] rsContext Reference to the context. + * @return The application exit code. 0 is no error. + */ +int ConfigureListener(const SContext& rsContext); + +/** + * @brief Add a listener to the system settings. + * @param[in] rssListener Reference to the string containing the listener configuration name. + * @param[in] rssConfig Reference to the string containing the listener configuration. + * @param[in] rsContext Reference to the context. + * @return The application exit code. 0 is no error. + */ +int AddListener(const std::string& rssListener, const std::string& rssConfig, const SContext& rsContext); + +/** + * @brief Remove a listener from the system settings. + * @param[in] rssListener Reference to the string containing the listener configuration name. + * @param[in] rsContext Reference to the context. + * @return The application exit code. 0 is no error. + */ +int RemoveListener(const std::string& rssListener, const SContext& rsContext); + +/** + * @brief Show a listener configuration. + * @param[in] rssListener Reference to the string containing the listener configuration name. + * @param[in] rsContext Reference to the context. + * @return The application exit code. 0 is no error. + */ +int ShowListenerConfig(const std::string& rssListener, const SContext& rsContext); + +/** + * @brief Configure the connection. + * @param[in] rsContext Reference to the context. + * @return The application exit code. 0 is no error. + */ +int ConfigureConnection(const SContext& rsContext); + +/** + * @brief Add a connection to the system settings. + * @param[in] rssConnection Reference to the string containing the connection configuration name. + * @param[in] rssConfig Reference to the string containing the connection configuration. + * @param[in] rsContext Reference to the context. + * @return The application exit code. 0 is no error. + */ +int AddConnection(const std::string& rssConnection, const std::string& rssConfig, const SContext& rsContext); + +/** + * @brief Remove a connection from the system settings. + * @param[in] rssConnection Reference to the string containing the connection configuration name. + * @param[in] rsContext Reference to the context. + * @return The application exit code. 0 is no error. + */ +int RemoveConnection(const std::string& rssConnection, const SContext& rsContext); + +/** + * @brief Show a connection configuration. + * @param[in] rssConnection Reference to the string containing the connection configuration name. + * @param[in] rsContext Reference to the context. + * @return The application exit code. 0 is no error. + */ +int ShowConnectionConfig(const std::string& rssConnection, const SContext& rsContext); + + +#endif // !defined CONNECTION_CONFIG_H diff --git a/sdv_executables/sdv_control/context.h b/sdv_executables/sdv_control/context.h index 49ad06b..e4932be 100644 --- a/sdv_executables/sdv_control/context.h +++ b/sdv_executables/sdv_control/context.h @@ -33,6 +33,7 @@ struct SContext bool bListShort = false; ///< Print only a shortened list with one column. sdv::sequence seqCmdLine; ///< The commands provided on the command line. std::filesystem::path pathInstallDir; ///< Optional installation directory. + std::string ssInsertBeforeConnection; ///< Insert before the provided connection }; #include // std::tolower diff --git a/sdv_executables/sdv_control/installation.cpp b/sdv_executables/sdv_control/installation.cpp index 4dffd9e..dc17d11 100644 --- a/sdv_executables/sdv_control/installation.cpp +++ b/sdv_executables/sdv_control/installation.cpp @@ -88,7 +88,7 @@ int Install(const SContext& rsContext) ssName = rsContext.seqCmdLine[1]; // Try to connect - sdv::TObjectPtr ptrRepository = sdv::com::ConnectToLocalServerRepository(rsContext.uiInstanceID); + sdv::TObjectPtr ptrRepository = sdv::com::ConnectToLocalServerRepository(); if (!ptrRepository) { if (!rsContext.bSilent) diff --git a/sdv_executables/sdv_control/list_elements.cpp b/sdv_executables/sdv_control/list_elements.cpp index 4a5bd7b..a4e1fbe 100644 --- a/sdv_executables/sdv_control/list_elements.cpp +++ b/sdv_executables/sdv_control/list_elements.cpp @@ -14,8 +14,10 @@ #include "list_elements.h" #include "print_table.h" #include +#include #include #include +#include #include "../../global/cmdlnparser/cmdlnparser.h" #include "../../global/exec_dir_helper.h" #include "../../global/flags.h" @@ -38,13 +40,14 @@ void ListHelp(const SContext& rsContext) } // Which list command help is requested - enum class EListCommand { unknown, modules, classes, components, installations, connections } eListCommand = EListCommand::unknown; + enum class EListCommand { unknown, modules, classes, components, installations, listeners, connections } eListCommand = EListCommand::unknown; if (rsContext.seqCmdLine.size() >= 2) { if (iequals(rsContext.seqCmdLine[1], "MODULES")) eListCommand = EListCommand::modules; else if (iequals(rsContext.seqCmdLine[1], "CLASSES")) eListCommand = EListCommand::classes; else if (iequals(rsContext.seqCmdLine[1], "COMPONENTS")) eListCommand = EListCommand::components; else if (iequals(rsContext.seqCmdLine[1], "INSTALLATIONS")) eListCommand = EListCommand::installations; + else if (iequals(rsContext.seqCmdLine[1], "LISTENERS")) eListCommand = EListCommand::listeners; else if (iequals(rsContext.seqCmdLine[1], "CONNECTIONS")) eListCommand = EListCommand::connections; } @@ -65,20 +68,24 @@ void ListHelp(const SContext& rsContext) case EListCommand::installations: CCommandLine::PrintHelpText(std::cout, "Usage: sdv_control LIST INSTALLATIONS [options...]\n\nShow a list of all installations.\n"); break; + case EListCommand::listeners: + CCommandLine::PrintHelpText(std::cout, "Usage: sdv_control LIST LISTENERS [options...]\n\nShow a list of all configured listeners.\n"); + break; case EListCommand::connections: - CCommandLine::PrintHelpText(std::cout, "Usage: sdv_control LIST CONNECTIONS [options...]\n\nShow a list of all connected applications.\n"); + CCommandLine::PrintHelpText(std::cout, "Usage: sdv_control LIST CONNECTIONS [options...]\n\nShow a list of all configured connections.\n"); break; default: - CCommandLine::PrintHelpText(std::cout, R"code(Usage: sdv_control LIST [options...] + CCommandLine::PrintHelpText(std::cout, R"text(Usage: sdv_control LIST [options...] Supported listing commands: LIST MODULES Show a list of loaded server modules. LIST CLASSES Show a list of available component classes. LIST COMPONENTS Show a list of instantiated server components LIST INSTALLATIONS Show a list of installations. + LIST LISTENERS Show a list of current listeners. LIST CONNECTIONS Show a list of current connections. -)code"); +)text"); break; } std::cout << "Options:\n"; @@ -103,33 +110,39 @@ int ListElements(const SContext& rsContext, std::ostream& rstream /*= std::cout* return CMDLN_ARG_ERR; } - // Which list command help is requested - enum class EListCommand { unknown, modules, classes, components, installations, connections } eListCommand = EListCommand::unknown; + // Which list command is requested + enum class EListCommand { unknown, modules, classes, components, installations, listeners, connections } eListCommand = EListCommand::unknown; if (rsContext.seqCmdLine.size() >= 2) { if (iequals(rsContext.seqCmdLine[1], "MODULES")) eListCommand = EListCommand::modules; else if (iequals(rsContext.seqCmdLine[1], "CLASSES")) eListCommand = EListCommand::classes; else if (iequals(rsContext.seqCmdLine[1], "COMPONENTS")) eListCommand = EListCommand::components; else if (iequals(rsContext.seqCmdLine[1], "INSTALLATIONS")) eListCommand = EListCommand::installations; + else if (iequals(rsContext.seqCmdLine[1], "LISTENERS")) eListCommand = EListCommand::listeners; else if (iequals(rsContext.seqCmdLine[1], "CONNECTIONS")) eListCommand = EListCommand::connections; } else { if (!rsContext.bSilent) { - std::cerr << "ERROR: " << CMDLN_ARG_ERR_MSG << " Missing listing command.." << std::endl << std::endl; + std::cerr << "ERROR: " << CMDLN_ARG_ERR_MSG << " Missing listing command." << std::endl; ListHelp(rsContext); } return CMDLN_ARG_ERR; } - // Try to connect - sdv::TObjectPtr ptrRepository = sdv::com::ConnectToLocalServerRepository(rsContext.uiInstanceID); - if (!ptrRepository) + // Try to connect (except when listing the listeners and connections... they can be read from the settings). + sdv::TObjectPtr ptrRepository; + if (eListCommand != EListCommand::listeners && eListCommand != EListCommand::connections) { - if (!rsContext.bSilent) - std::cerr << "ERROR: " << CONNECT_SDV_SERVER_ERROR_MSG << " Instance #" << rsContext.uiInstanceID << "." << std::endl; - return CONNECT_SDV_SERVER_ERROR; + ptrRepository = sdv::com::ConnectToLocalServerRepository(); + if (!ptrRepository) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << CONNECT_SDV_SERVER_ERROR_MSG << " Instance #" << rsContext.uiInstanceID << "." + << std::endl; + return CONNECT_SDV_SERVER_ERROR; + } } int iRet = -100; @@ -147,12 +160,16 @@ int ListElements(const SContext& rsContext, std::ostream& rstream /*= std::cout* case EListCommand::installations: iRet = ListInstallations(rsContext, ptrRepository, rstream); break; + case EListCommand::listeners: + iRet = ListListeners(rsContext, rstream); + break; case EListCommand::connections: - iRet = ListConnections(rsContext, ptrRepository, rstream); + iRet = ListConnections(rsContext, rstream); break; default: break; } + rstream << std::endl; return iRet; } @@ -388,8 +405,107 @@ int ListInstallations(const SContext& rsContext, const sdv::TObjectPtr& rptrRepo return NO_ERROR; } -int ListConnections(const SContext& /*rsContext*/, const sdv::TObjectPtr& /*rptrRepository*/, std::ostream& /*rstream = std::cout */ ) +std::string BuildParameters(const sdv::toml::CNodeCollection& rtable, const std::string& ssPrefix = std::string()) { - std::cout << "ERROR: " << NOT_IMPLEMENTED_MSG << " :-(" << std::endl; - return NOT_IMPLEMENTED; + std::string ssParams; + for (uint32_t uiIndex = 0; uiIndex < rtable.GetCount(); uiIndex++) + { + sdv::toml::CNode node = rtable.Get(uiIndex); + if (!node) continue; + std::string ssNodeText; + if (node.GetType() == sdv::toml::ENodeType::node_table) + { + sdv::toml::CNodeCollection tableChild(node); + if (!tableChild) continue; + ssNodeText = BuildParameters(tableChild, static_cast(tableChild.GetName()) + "."); + } + else if (node.GetType() == sdv::toml::ENodeType::node_array) + { + // TODO... + } + else + ssNodeText = ssPrefix + node.GetName() + "=" + node.GetValueAsString(); + + // Add the text + if (!ssParams.empty()) + ssParams += ", "; + ssParams += ssNodeText; + } + return ssParams; +} + +int ListListeners(const SContext& rsContext, std::ostream& rstream /*= std::cout */ ) +{ + std::vector> vecListenerList; + vecListenerList.push_back({"Listener", "Provider", "Parameters"}); + + // Request the settings. + auto ptrAppService = sdv::core::GetObject("AppSettingsService"); + sdv::app::IAppSettingsPersist* pAppSettingsPersist = ptrAppService.GetInterface(); + sdv::app::IAppConnections* pAppConnections = ptrAppService.GetInterface(); + if (!pAppSettingsPersist || !pAppConnections) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << CANNOT_LOAD_SETTINGS_MSG << std::endl; + return CANNOT_LOAD_SETTINGS; + } + auto seqListeners = pAppConnections->GetListeners(); + for (const sdv::u8string& rssListener : seqListeners) + { + std::string ssListener = rssListener; + auto ssConfig = pAppConnections->GetListenerConfig(rssListener); + sdv::toml::CTOMLParser parser(ssConfig); + if (!parser) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << PROCESS_LISTENER_CONFIG_ERROR_MSG << std::endl; + return PROCESS_LISTENER_CONFIG_ERROR; + } + std::string ssProvider = parser.GetDirect("Provider.Name").GetValueAsString(); + std::string ssParams = BuildParameters(parser.GetDirect("IpcChannel")); + vecListenerList.push_back({ssListener, ssProvider, ssParams}); + } + + PrintTable(vecListenerList, rstream, rsContext.bListNoHdr); + if (vecListenerList.size() < 2) + rstream << " No listeners configured!" << std::endl; + return NO_ERROR; +} + +int ListConnections(const SContext& rsContext, std::ostream& rstream /*= std::cout */ ) +{ + std::vector> vecConnectionList; + vecConnectionList.push_back({"Connection", "Provider", "Parameters"}); + + // Request the settings. + auto ptrAppService = sdv::core::GetObject("AppSettingsService"); + sdv::app::IAppSettingsPersist* pAppSettingsPersist = ptrAppService.GetInterface(); + sdv::app::IAppConnections* pAppConnections = ptrAppService.GetInterface(); + if (!pAppSettingsPersist || !pAppConnections || !pAppSettingsPersist->LoadSettings()) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << CANNOT_LOAD_SETTINGS_MSG << std::endl; + return CANNOT_LOAD_SETTINGS; + } + auto seqConnections = pAppConnections->GetConnections(); + for (const sdv::u8string& rssConnection : seqConnections) + { + std::string ssConnection = rssConnection; + auto ssConfig = pAppConnections->GetConnectionConfig(rssConnection); + sdv::toml::CTOMLParser parser(ssConfig); + if (!parser) + { + if (!rsContext.bSilent) + std::cerr << "ERROR: " << PROCESS_CONNECTION_CONFIG_ERROR_MSG << std::endl; + return PROCESS_CONNECTION_CONFIG_ERROR; + } + std::string ssProvider = parser.GetDirect("Provider.Name").GetValueAsString(); + std::string ssParams = BuildParameters(parser.GetDirect("IpcChannel")); + vecConnectionList.push_back({ssConnection, ssProvider, ssParams}); + } + + PrintTable(vecConnectionList, rstream, rsContext.bListNoHdr); + if (vecConnectionList.size() < 2) + rstream << " No connections configured!" << std::endl; + return NO_ERROR; } diff --git a/sdv_executables/sdv_control/list_elements.h b/sdv_executables/sdv_control/list_elements.h index 2515a18..80952c3 100644 --- a/sdv_executables/sdv_control/list_elements.h +++ b/sdv_executables/sdv_control/list_elements.h @@ -71,12 +71,19 @@ int ListComponents(const SContext& rsContext, const sdv::TObjectPtr& rptrReposit int ListInstallations(const SContext& rsContext, const sdv::TObjectPtr& rptrRepository, std::ostream& rstream); /** - * @brief List the current connections. + * @brief List the current configured listeners. * @param[in] rsContext Reference to the context. - * @param[in] rptrRepository Reference to the saerver repository. * @param[in] rstream The output stream to use for printing (table only). * @return The application exit code. 0 is no error. */ -int ListConnections(const SContext& rsContext, const sdv::TObjectPtr& rptrRepository, std::ostream& rstream); +int ListListeners(const SContext& rsContext, std::ostream& rstream); + +/** + * @brief List the current connections. + * @param[in] rsContext Reference to the context. + * @param[in] rstream The output stream to use for printing (table only). + * @return The application exit code. 0 is no error. + */ +int ListConnections(const SContext& rsContext, std::ostream& rstream); #endif // !defined LIST_ELEMENTS_H \ No newline at end of file diff --git a/sdv_executables/sdv_control/main.cpp b/sdv_executables/sdv_control/main.cpp index 86ed273..434ff34 100644 --- a/sdv_executables/sdv_control/main.cpp +++ b/sdv_executables/sdv_control/main.cpp @@ -27,6 +27,7 @@ #include "list_elements.h" #include "start_stop_service.h" #include "installation.h" +#include "connection_config.h" #include "../error_msg.h" /** @@ -53,12 +54,6 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) // NOTE EVE 27.05.2025: This task has been taken over by the process watchdog. CProcessWatchdog watchdog; - // If not set, set the runtime location to the EXE directory. - if (sdv::app::CAppControl::GetFrameworkRuntimeDirectory().empty()) - sdv::app::CAppControl::SetFrameworkRuntimeDirectory(GetExecDirectory()); - if (sdv::app::CAppControl::GetComponentInstallDirectory().empty()) - sdv::app::CAppControl::SetComponentInstallDirectory(GetExecDirectory()); - CCommandLine cmdln(static_cast(CCommandLine::EParseFlags::no_assignment_character)); SContext sContext; bool bError = false; @@ -67,24 +62,40 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) std::string ssArgError; try { - auto& rArgHelpDef = cmdln.DefineOption("?", bHelp, "Show help. Use --help for specific help on the command."); + // Argument groups 1 - 10: + // STARTUP + // SHUTDOWN + // LIST + // INSTALL + // UPDATE + // UNINSTALL + // START + // STOP + // LISTENER + // CONNECTION + auto& rArgHelpDef = + cmdln.DefineOption("?", bHelp, "Show help. Use --help for specific help on the command."); rArgHelpDef.AddSubOptionName("help"); - auto& rArgSilentDef = cmdln.DefineOption("s", sContext.bSilent, "Do not show any information on std::cout. Not compatible with 'verbose'."); + auto& rArgSilentDef = cmdln.DefineOption("s", sContext.bSilent, "Do not show any information on std::cout. Not compatible " + "with 'verbose'."); rArgSilentDef.AddSubOptionName("silent"); - auto& rArgVerboseDef = cmdln.DefineOption("v", sContext.bVerbose, "Provide verbose information. Not compatible with 'silent'."); + auto& rArgVerboseDef = cmdln.DefineOption("v", sContext.bVerbose, "Provide verbose information. Not compatible with " + "'silent'."); rArgVerboseDef.AddSubOptionName("verbose"); cmdln.DefineSubOption("version", bVersion, "Show version information."); cmdln.DefineSubOption("instance", sContext.uiInstanceID, "The instance ID of the SDV instance (default ID is 1000)."); cmdln.DefineSubOption("server_silent", sContext.bServerSilent, "Only used with STARTUP command: Server is started using " - "silent option. Not compatible with 'server_verbose'."); + "silent option. Not compatible with 'server_verbose'.", true, 1); cmdln.DefineSubOption("server_verbose", sContext.bServerVerbose, "Only used with STARTUP command: Server is started using " - "verbose option. Not compatible with 'server_silent'."); + "verbose option. Not compatible with 'server_silent'.", true, 1); cmdln.DefineSubOption("install_dir", sContext.pathInstallDir, "Only used with STARTUP command: Installation directory " - "(absolute or relative to the sdv_core executable)."); + "(absolute or relative to the sdv_core executable).", true, 1); cmdln.DefineSubOption("no_header", sContext.bListNoHdr, "Only used with LIST command: Do not print a header for the " - "listing table."); + "listing table.", true, 3); cmdln.DefineSubOption("short", sContext.bListShort, "Only used with LIST command: Print only the most essential " - "information as one column."); + "information as one column.", true, 3); + cmdln.DefineSubOption( + "insert_before", sContext.ssInsertBeforeConnection, "Name of the connection to insert before.", true, 10); cmdln.DefineDefaultArgument(sContext.seqCmdLine, "COMMAND"); cmdln.Parse(static_cast(iArgc), rgszArgv); @@ -98,7 +109,7 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) if (!sContext.bSilent) { std::cout << "SDV Server Control Utility" << std::endl; - std::cout << "Copyright (C): 2022-2025 ZF Friedrichshafen AG" << std::endl; + std::cout << "Copyright (C): 2022-2026 ZF Friedrichshafen AG" << std::endl; std::cout << "Author: Erik Verhoeven" << std::endl << std::endl; } @@ -113,7 +124,7 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) bError = true; } - enum class ECommand { unknown, startup, shutdown, list, install, update, uninstall, start, stop } eCommand = ECommand::unknown; + enum class ECommand { unknown, startup, shutdown, list, install, update, uninstall, start, stop, listener, connection } eCommand = ECommand::unknown; if (!sContext.seqCmdLine.empty()) { if (iequals(sContext.seqCmdLine[0], "STARTUP")) eCommand = ECommand::startup; @@ -124,6 +135,8 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) else if (iequals(sContext.seqCmdLine[0], "UNINSTALL")) eCommand = ECommand::uninstall; else if (iequals(sContext.seqCmdLine[0], "START")) eCommand = ECommand::start; else if (iequals(sContext.seqCmdLine[0], "STOP")) eCommand = ECommand::stop; + else if (iequals(sContext.seqCmdLine[0], "LISTENER")) eCommand = ECommand::listener; + else if (iequals(sContext.seqCmdLine[0], "CONNECTION")) eCommand = ECommand::connection; else { if (!sContext.bSilent) @@ -157,17 +170,25 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) case ECommand::uninstall: InstallationHelp(sContext); break; + case ECommand::listener: + ListenerHelp(sContext); + break; + case ECommand::connection: + ConnectionHelp(sContext); + break; default: - cmdln.PrintHelp(std::cout, R"code(Supported commands: - STARTUP Start the core application server - SHUTDOWN Stop the core application server - LIST List module/classes/component/installation/connection information. - INSTALL Install a new application or service. - UPDATE Update an existing installation. - UNINSTALL Uninstall an installed application or service. - START Start a service (complex services only). - STOP Stop a service (complex services only). -)code"); + cmdln.PrintHelp(std::cout, R"text(Supported commands: + STARTUP Start the core application server + SHUTDOWN Stop the core application server + LIST List information about modules/classes/components/installations/listeners/connections. + INSTALL Install a new application or service. + UPDATE Update an existing installation. + UNINSTALL Uninstall an installed application or service. + START Start a service (complex services and vehicle functions only). + STOP Stop a service (complex services and vehicle functions only). + LISTENER Add or remove a communication listener configuration. + CONNECTION Add or remove a connection configuration. +)text"); break; } } @@ -185,11 +206,11 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) if (sContext.bVerbose) std::cout << "Starting local application control.." << std::endl; sdv::app::CAppControl appcontrol; - std::string ssAppConfig = std::string(R"code( + std::string ssAppConfig = std::string(R"toml( [Application] Mode = "Maintenance" -Instance = )code") + std::to_string(sContext.uiInstanceID) + R"code( -)code"; +Instance = )toml") + std::to_string(sContext.uiInstanceID) + R"toml( +)toml"; if (!appcontrol.Startup(ssAppConfig)) { if (!sContext.bSilent) @@ -224,6 +245,12 @@ Instance = )code") + std::to_string(sContext.uiInstanceID) + R"code( case ECommand::uninstall: iRet = Uninstall(sContext); break; + case ECommand::listener: + iRet = ConfigureListener(sContext); + break; + case ECommand::connection: + iRet = ConfigureConnection(sContext); + break; default: std::cout << "Command missing :-(" << std::endl; break; diff --git a/sdv_executables/sdv_control/start_stop_service.cpp b/sdv_executables/sdv_control/start_stop_service.cpp index 879111e..c830e21 100644 --- a/sdv_executables/sdv_control/start_stop_service.cpp +++ b/sdv_executables/sdv_control/start_stop_service.cpp @@ -35,8 +35,8 @@ void StartStopServiceHelp(const SContext& rsContext) " sdv_control START [options...]\n\n" "Start a complex service with the supplied object name as is defined in the configuration (first usage) or the " "supplied class name and with object name assignment and add to the configuration (second usage).\n" - "Only complex services can be started. If the service depends on not running other services, these are started as " - "well.\n\n"); + "Only vehicle functions and complex services can be started. If the service depends on not running other services, " + "these are started as well.\n\n"); return; } if (iequals(rsContext.seqCmdLine[0], "STOP")) @@ -47,15 +47,14 @@ void StartStopServiceHelp(const SContext& rsContext) " sdv_control STOP [options...]\n\n" "Stop the complex service(s) with the supplied object name (first usage), with supplied object ID (second usage), with " "supplied class name (third usage) or contained in the module with the supplied module name (fourth usage).\n" - "Only complex services can be stopped. Dependent services are stopped as well. If one of object to be stopped is not a " - "complex service, the command fails.\n\n"); + "Only vehicle functions and complex services can be stopped. Dependent services are stopped as well. If one of object " + "to be stopped is not a vehicle function or a complex service, the command fails.\n\n"); return; } if (!rsContext.bSilent) std::cerr << "ERROR: invalid start/stop command..." << std::endl; } - int StartService(const SContext& rsContext) { // First argument should be "START" @@ -88,7 +87,7 @@ int StartService(const SContext& rsContext) ssName = rsContext.seqCmdLine[1]; // Try to connect - sdv::TObjectPtr ptrRepository = sdv::com::ConnectToLocalServerRepository(rsContext.uiInstanceID); + sdv::TObjectPtr ptrRepository = sdv::com::ConnectToLocalServerRepository(); if (!ptrRepository) { if (!rsContext.bSilent) @@ -154,7 +153,7 @@ int StopService(const SContext& rsContext) ssName = rsContext.seqCmdLine[1]; // Try to connect - sdv::TObjectPtr ptrRepository = sdv::com::ConnectToLocalServerRepository(rsContext.uiInstanceID); + sdv::TObjectPtr ptrRepository = sdv::com::ConnectToLocalServerRepository(); if (!ptrRepository) { if (!rsContext.bSilent) diff --git a/sdv_executables/sdv_control/startup_shutdown.cpp b/sdv_executables/sdv_control/startup_shutdown.cpp index 57c8c0f..e29d77b 100644 --- a/sdv_executables/sdv_control/startup_shutdown.cpp +++ b/sdv_executables/sdv_control/startup_shutdown.cpp @@ -76,9 +76,9 @@ void StartupShutdownHelp(const SContext& rsContext) "Start the SDV server with the supplied instance ID. If no instance ID is supplied, use the default instance " "ID #1000.\n\n" "Options:\n" - " --server_silent Server is started using silent option. Not compatible with 'server_verbose'.\n" - " --server_verbose Server is started using verbose option. Not compatible with 'server_silent'.\n" - " --install_dir Installation directory (absolute or relative to the sdv_core executable).\n\n"); + " --server_silent Server is started using silent option. Not compatible with 'server_verbose'.\n" + " --server_verbose Server is started using verbose option. Not compatible with 'server_silent'.\n" + " --install_dir Installation directory (absolute or relative to the sdv_core executable).\n\n"); return; } if (iequals(rsContext.seqCmdLine[0], "SHUTDOWN")) @@ -156,7 +156,7 @@ int StartupSDVServer(const SContext& rsContext) } // Try to connect - auto ptrRepository = sdv::com::ConnectToLocalServerRepository(rsContext.uiInstanceID); + auto ptrRepository = sdv::com::ConnectToLocalServerRepository(); if (!ptrRepository) { if (!rsContext.bSilent) @@ -183,7 +183,7 @@ int ShutdownSDVServer(const SContext& rsContext) std::cout << "Connecting to the SDV #" << rsContext.uiInstanceID << " server..." << std::endl; // Try to connect - auto ptrRepository = sdv::com::ConnectToLocalServerRepository(rsContext.uiInstanceID); + auto ptrRepository = sdv::com::ConnectToLocalServerRepository(); if (!ptrRepository) { if (!rsContext.bSilent) diff --git a/sdv_executables/sdv_core/main.cpp b/sdv_executables/sdv_core/main.cpp index 012c37d..857b100 100644 --- a/sdv_executables/sdv_core/main.cpp +++ b/sdv_executables/sdv_core/main.cpp @@ -18,6 +18,40 @@ #include #include "../error_msg.h" +#ifdef _WIN32 +/** + * @brief Console application control handler (CTRL+C, close, shutdown, etc.). + * @param[in] dwCtrlType The control type triggered by the system. + * @return Returns TRUE when the signal was handled. + */ +static BOOL WINAPI ControlHandler(DWORD dwCtrlType) +{ + switch (dwCtrlType) + { + case CTRL_C_EVENT: + case CTRL_BREAK_EVENT: + case CTRL_CLOSE_EVENT: + case CTRL_LOGOFF_EVENT: + case CTRL_SHUTDOWN_EVENT: + return TRUE; + break; + default: + return FALSE; + } +} +#elif defined __unix__ +#include + + /** + * @brief Signal handler to catch signal events. + * @param[in] iSigNum The signal type that was triggered. + */ +static void SignalHandler(int /*iSigNum*/) +{ + // Do nothing... +} +#endif + /** * @brief Main function of the executable. * @param[in] iArgc Amount of arguments provided. @@ -46,12 +80,6 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) if (thread.joinable()) thread.join(); - // If not set, set the runtime location to the EXE directory. - if (sdv::app::CAppControl::GetFrameworkRuntimeDirectory().empty()) - sdv::app::CAppControl::SetFrameworkRuntimeDirectory(GetExecDirectory()); - if (sdv::app::CAppControl::GetComponentInstallDirectory().empty()) - sdv::app::CAppControl::SetComponentInstallDirectory(GetExecDirectory()); - CCommandLine cmdln(static_cast(CCommandLine::EParseFlags::no_assignment_character)); bool bHelp = false; bool bError = false; @@ -91,7 +119,7 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) if (!bSilent && !bNoBanner) { std::cout << "SDV core application" << std::endl; - std::cout << "Copyright (C): 2022-2025 ZF Friedrichshafen AG" << std::endl; + std::cout << "Copyright (C): 2022-2026 ZF Friedrichshafen AG" << std::endl; std::cout << "Author: Erik Verhoeven" << std::endl << std::endl; } @@ -185,14 +213,40 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) return APP_CONTROL_STARTUP_ERROR; } +#ifdef _WIN32 + // Register the console control handler + if (!SetConsoleCtrlHandler(&ControlHandler, TRUE)) + { + if (!bSilent) + std::cerr << "ERROR: " << APP_CONTROL_START_LOOP_FAILED_MSG << " - failed registering control handler." << std::endl; + return APP_CONTROL_START_LOOP_FAILED; + } +#elif defined __unix__ + // Install our signal handler + struct sigaction sSigAction + {}; + sSigAction.sa_handler = SignalHandler; + sSigAction.sa_flags = SA_RESTART; + if (sigaction(SIGINT, &sSigAction, NULL) == -1) + { + if (!bSilent) + std::cerr << "ERROR: " << APP_CONTROL_START_LOOP_FAILED_MSG << " - failed registering control handler." << std::endl; + return APP_CONTROL_START_LOOP_FAILED; + } +#endif + // Start the running loop if (!appcontrol.RunLoop()) { if (!bSilent) - std::cerr << "ERROR: " << APP_CONTROL_DUPLICATE_INSTANCE_MSG << std::endl; - return APP_CONTROL_DUPLICATE_INSTANCE; + std::cerr << "ERROR: " << APP_CONTROL_START_LOOP_FAILED_MSG << std::endl; + return APP_CONTROL_START_LOOP_FAILED; } +#ifdef _WIN32 + SetConsoleCtrlHandler(&ControlHandler, FALSE); +#endif + if (bVerbose) { std::cout << "Shutdown request received..." << std::endl; diff --git a/sdv_executables/sdv_dbc_util/fmu_templates.h b/sdv_executables/sdv_dbc_util/fmu_templates.h index 3505e95..485e028 100644 --- a/sdv_executables/sdv_dbc_util/fmu_templates.h +++ b/sdv_executables/sdv_dbc_util/fmu_templates.h @@ -372,7 +372,10 @@ bool CreateCoreServiceTomlFile(const std::string& resources) std::ofstream tomlFile("sdv_core_reloc.toml"); if (tomlFile.is_open()) { - tomlFile << "# Location of the SDV binaries and configuration files\ndirectory = \""; + tomlFile << R"toml(# Location of the SDV binaries and configuration files +[CoreLocation] +Version = 100 +Framework = ")toml"; tomlFile << resources; tomlFile << "\"\n"; tomlFile.close(); diff --git a/sdv_executables/sdv_dbc_util/main.cpp b/sdv_executables/sdv_dbc_util/main.cpp index 6cfc0d0..1bf81d6 100644 --- a/sdv_executables/sdv_dbc_util/main.cpp +++ b/sdv_executables/sdv_dbc_util/main.cpp @@ -82,7 +82,7 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) if (!bSilent) { std::cout << "DBC utility" << std::endl; - std::cout << "Copyright (C): 2022-2025 ZF Friedrichshafen AG" << std::endl; + std::cout << "Copyright (C): 2022-2026 ZF Friedrichshafen AG" << std::endl; std::cout << "Author: Erik Verhoeven" << std::endl; } if (bHelp) diff --git a/sdv_executables/sdv_idl_compiler/generator/proxy_generator.cpp b/sdv_executables/sdv_idl_compiler/generator/proxy_generator.cpp index a53432b..cb5f889 100644 --- a/sdv_executables/sdv_idl_compiler/generator/proxy_generator.cpp +++ b/sdv_executables/sdv_idl_compiler/generator/proxy_generator.cpp @@ -174,6 +174,9 @@ std::string CProxyGenerator::GetFuncImpl(const SFuncInfo& rsFunc, CKeywordMap& r else sstreamExceptions << R"code(sdv::XUnknownException exception; exception.unknown_id = except_id; + // Cpp-Check 2.7 warns about the uninitialized variable _id. This variable is allocated as inline static const and is + // assigned a value. Suppress the warning. + // cppcheck-suppress uninitvar throw exception;)code"; for (const std::string& rssException : rvecExceptions) { @@ -181,6 +184,9 @@ std::string CProxyGenerator::GetFuncImpl(const SFuncInfo& rsFunc, CKeywordMap& r { )code" << rssException << R"code( exception; desOutput >> exception; + // Cpp-Check 2.7 warns about the uninitialized variable _id. This variable is allocated as inline static const and is + // assigned a value. Suppress the warning. + // cppcheck-suppress uninitvar throw exception; } )code"; @@ -190,6 +196,9 @@ std::string CProxyGenerator::GetFuncImpl(const SFuncInfo& rsFunc, CKeywordMap& r { sdv::XUnknownException exception; exception.unknown_id = except_id; + // Cpp-Check 2.7 warns about the uninitialized variable _id. This variable is allocated as inline static const and is + // assigned a value. Suppress the warning. + // cppcheck-suppress uninitvar throw exception; } })code"; diff --git a/sdv_executables/sdv_idl_compiler/main.cpp b/sdv_executables/sdv_idl_compiler/main.cpp index 4723265..d6781a7 100644 --- a/sdv_executables/sdv_idl_compiler/main.cpp +++ b/sdv_executables/sdv_idl_compiler/main.cpp @@ -41,7 +41,7 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) CProcessWatchdog watchdog; std::cout << "SDV IDL Compiler" << std::endl; - std::cout << "Copyright (C): 2022-2025 ZF Friedrichshafen AG" << std::endl; + std::cout << "Copyright (C): 2022-2026 ZF Friedrichshafen AG" << std::endl; std::cout << "Author: Erik Verhoeven" << std::endl; // Create a list of UTF-8 arguments diff --git a/sdv_executables/sdv_idl_compiler/source.cpp b/sdv_executables/sdv_idl_compiler/source.cpp index cbe7e0c..2fef48a 100644 --- a/sdv_executables/sdv_idl_compiler/source.cpp +++ b/sdv_executables/sdv_idl_compiler/source.cpp @@ -28,6 +28,7 @@ #include "token.h" #include #include +#include #include #include #include @@ -71,7 +72,7 @@ const std::string& CSource::GetCodeRef() const void CSource::ReadFile(const std::filesystem::path& rpath) { // Set the locale to work with UTF8 - std::setlocale(LC_ALL, "en_US.utf8"); + ::setlocale(LC_ALL, "en_US.utf8"); if (rpath.empty()) throw CCompileException("File name missing"); diff --git a/sdv_executables/sdv_iso/main.cpp b/sdv_executables/sdv_iso/main.cpp index d979154..4b51128 100644 --- a/sdv_executables/sdv_iso/main.cpp +++ b/sdv_executables/sdv_iso/main.cpp @@ -22,6 +22,40 @@ #include #include "../error_msg.h" +#ifdef _WIN32 +/** + * @brief Console application control handler (CTRL+C, close, shutdown, etc.). + * @param[in] dwCtrlType The control type triggered by the system. + * @return Returns TRUE when the signal was handled. + */ +static BOOL WINAPI ControlHandler(DWORD dwCtrlType) +{ + switch (dwCtrlType) + { + case CTRL_C_EVENT: + case CTRL_BREAK_EVENT: + case CTRL_CLOSE_EVENT: + case CTRL_LOGOFF_EVENT: + case CTRL_SHUTDOWN_EVENT: + return TRUE; + break; + default: + return FALSE; + } +} +#elif defined __unix__ +#include + + /** + * @brief Signal handler to catch signal events. + * @param[in] iSigNum The signal type that was triggered. + */ +static void SignalHandler(int /*iSigNum*/) +{ + // Do nothing... +} +#endif + /** * @brief Connect event callback wrapper. Calls shutdown request on disconnect or error. */ @@ -93,12 +127,6 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) if (thread.joinable()) thread.join(); - // If not set, set the runtime location to the EXE directory. - if (sdv::app::CAppControl::GetFrameworkRuntimeDirectory().empty()) - sdv::app::CAppControl::SetFrameworkRuntimeDirectory(GetExecDirectory()); - if (sdv::app::CAppControl::GetComponentInstallDirectory().empty()) - sdv::app::CAppControl::SetComponentInstallDirectory(GetExecDirectory()); - CCommandLine cmdln(static_cast(CCommandLine::EParseFlags::no_assignment_character)); bool bHelp = false; bool bError = false; @@ -126,23 +154,29 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) { ssArgError = rsExcept.what(); bHelp = true; - bError = true; } if (!bSilent) { std::cout << "SDV isolation application" << std::endl; - std::cout << "Copyright (C): 2022-2025 ZF Friedrichshafen AG" << std::endl; + std::cout << "Copyright (C): 2022-2026 ZF Friedrichshafen AG" << std::endl; std::cout << "Author: Erik Verhoeven" << std::endl << std::endl; } - if (!ssArgError.empty() && !bSilent) + if (!ssArgError.empty() && !bSilent && !bHelp && !bVersion) std::cerr << "ERROR: " << ssArgError << std::endl; - if (ssConfigBase64.empty()) + if (bVersion || bVerbose) { - ssArgError = "Missing app configuration."; - bHelp = true; + std::cout << "Version: " << (SDVFrameworkBuildVersion / 100) << "." << (SDVFrameworkBuildVersion % 100) << " build " + << SDVFrameworkSubbuildVersion << " interface " << SDVFrameworkInterfaceVersion << std::endl; + } + + if (!bHelp && ssConfigBase64.empty()) + { + if (!bSilent && !bVersion) + std::cout << "Missing app configuration."; + bError = true; } if (bHelp) @@ -153,17 +187,14 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) std::cout << std::endl; cmdln.PrintHelp(std::cout, "The isolation application is used to isolate components from the core process. The " "isolation aims to improve the overall stability of the system.\n"); - return bError ? CMDLN_ARG_ERR : NO_ERROR; } + return (bError && !bVersion && !bHelp) ? CMDLN_ARG_ERR : NO_ERROR; } - if (bError) return CMDLN_ARG_ERR; + if (bError) return (bError && !bVersion && !bHelp) ? CMDLN_ARG_ERR : NO_ERROR; // Check for encoded configuration std::string ssIsoAppConfig = Base64DecodePlainText(ssConfigBase64); - if (bVersion || bVerbose) - std::cout << "Version: " << (SDVFrameworkBuildVersion / 100) << "." << (SDVFrameworkBuildVersion % 100) << " build " << - SDVFrameworkSubbuildVersion << " interface " << SDVFrameworkInterfaceVersion << std::endl; if (!bSilent) std::cout << "Instance ID: " << uiInstanceID << std::endl; @@ -267,7 +298,7 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) std::cerr << "ERROR: " << LINK_REPO_SERVICE_ERROR_MSG << std::endl; return LINK_REPO_SERVICE_ERROR; } - pLinkCoreRepo->LinkCoreRepository(pCoreRepo); + sdv::core::TLinkID tRepoLink = pLinkCoreRepo->LinkCoreRepository(pCoreRepo); // Create the one object... is this a service or a utility? std::string ssClassName = parser.GetDirect("Isolation.Class").GetValue(); @@ -332,6 +363,28 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) // The lifetime of the object is now taken over by the core or if failed, the object will be destroyed. ptrObject.Clear(); +#ifdef _WIN32 + // Register the console control handler + if (!SetConsoleCtrlHandler(&ControlHandler, TRUE)) + { + if (!bSilent) + std::cerr << "ERROR: " << APP_CONTROL_START_LOOP_FAILED_MSG << " - failed registering control handler." << std::endl; + return APP_CONTROL_START_LOOP_FAILED; + } +#elif defined __unix__ + // Install our signal handler + struct sigaction sSigAction + {}; + sSigAction.sa_handler = SignalHandler; + sSigAction.sa_flags = SA_RESTART; + if (sigaction(SIGINT, &sSigAction, NULL) == -1) + { + if (!bSilent) + std::cerr << "ERROR: " << APP_CONTROL_START_LOOP_FAILED_MSG << " - failed registering control handler." << std::endl; + return APP_CONTROL_START_LOOP_FAILED; + } +#endif + // Deactivate the configuration mode. appcontrol.SetRunningMode(); @@ -339,10 +392,14 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) if (!appcontrol.RunLoop()) { if (!bSilent) - std::cerr << "ERROR: " << APP_CONTROL_START_LOOP_FAIELD_MSG << std::endl; - return APP_CONTROL_START_LOOP_FAIELD; + std::cerr << "ERROR: " << APP_CONTROL_START_LOOP_FAILED_MSG << std::endl; + return APP_CONTROL_START_LOOP_FAILED; } +#ifdef _WIN32 + SetConsoleCtrlHandler(&ControlHandler, FALSE); +#endif + // TODO EVE if (appcontrol.IsConfiguring()) std::cout << "Iso app configuring... " << __LINE__ << std::endl; @@ -358,7 +415,7 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) // Shutdwown pConnect->UnregisterStateEventCallback(uiCookie); parser.Clear(); - pLinkCoreRepo->UnlinkCoreRepository(); + pLinkCoreRepo->UnlinkCoreRepository(tRepoLink); appcontrol.Shutdown(); if (!bSilent) diff --git a/sdv_executables/sdv_local_shutdown/main.cpp b/sdv_executables/sdv_local_shutdown/main.cpp index b103e16..1a5d8c0 100644 --- a/sdv_executables/sdv_local_shutdown/main.cpp +++ b/sdv_executables/sdv_local_shutdown/main.cpp @@ -32,12 +32,6 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) // NOTE EVE 27.05.2025: This task has been taken over by the process watchdog. CProcessWatchdog watchdog; - // If not set, set the runtime location to the EXE directory. - if (sdv::app::CAppControl::GetFrameworkRuntimeDirectory().empty()) - sdv::app::CAppControl::SetFrameworkRuntimeDirectory(GetExecDirectory()); - if (sdv::app::CAppControl::GetComponentInstallDirectory().empty()) - sdv::app::CAppControl::SetComponentInstallDirectory(GetExecDirectory()); - CCommandLine cmdln(static_cast(CCommandLine::EParseFlags::no_assignment_character)); bool bHelp = false; bool bError = false; @@ -67,7 +61,7 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) if (!bSilent) { std::cout << "SDV Local Instance Shutdown Utility" << std::endl; - std::cout << "Copyright (C): 2022-2025 ZF Friedrichshafen AG" << std::endl; + std::cout << "Copyright (C): 2022-2026 ZF Friedrichshafen AG" << std::endl; std::cout << "Author: Erik Verhoeven" << std::endl << std::endl; } diff --git a/sdv_executables/sdv_packager/core_control.cpp b/sdv_executables/sdv_packager/core_control.cpp index a58493a..bdd79e4 100644 --- a/sdv_executables/sdv_packager/core_control.cpp +++ b/sdv_executables/sdv_packager/core_control.cpp @@ -24,4 +24,5 @@ #include "../../sdv_services/core/toml_parser/character_reader_utf_8.cpp" #include "../../sdv_services/core/toml_parser/miscellaneous.cpp" #include "../../sdv_services/core/toml_parser/code_snippet.cpp" +#include "../../sdv_services/core/toml_parser/parser_node_indexer.cpp" diff --git a/sdv_executables/sdv_packager/environment.cpp b/sdv_executables/sdv_packager/environment.cpp index ef57ceb..27d05f7 100644 --- a/sdv_executables/sdv_packager/environment.cpp +++ b/sdv_executables/sdv_packager/environment.cpp @@ -12,9 +12,8 @@ ********************************************************************************/ #include "environment.h" - #include - +#include "../../global/exec_dir_helper.h" #include "../../global/cmdlnparser/cmdlnparser.cpp" CSdvPackagerEnvironment::CSdvPackagerEnvironment() : @@ -862,13 +861,16 @@ bool CSdvPackagerEnvironment::ProcessCommandLine(const std::vector& { #ifdef _WIN32 const wchar_t* szInstallDir = _wgetenv(L"SDV_COMPONENT_INSTALL"); + if (!szInstallDir) szInstallDir = _wgetenv(L"SDV_FRAMEWORK_RUNTIME"); if (szInstallDir) m_pathTargetLocation = szInstallDir; #elif defined __unix__ const char* szInstallDir = getenv("SDV_COMPONENT_INSTALL"); + if (!szInstallDir) szInstallDir = getenv("SDV_FRAMEWORK_RUNTIME"); if (szInstallDir) m_pathTargetLocation = std::filesystem::u8path(szInstallDir); #else #error OS not supported! #endif + if (m_pathTargetLocation.empty()) m_pathTargetLocation = GetExecDirectory(); } // Is the directory existing? If not, create the directory diff --git a/sdv_executables/sdv_packager/main.cpp b/sdv_executables/sdv_packager/main.cpp index 28f40c3..2b30e9f 100644 --- a/sdv_executables/sdv_packager/main.cpp +++ b/sdv_executables/sdv_packager/main.cpp @@ -41,12 +41,6 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) // NOTE EVE 27.05.2025: This task has been taken over by the process watchdog. CProcessWatchdog watchdog; - // If not set, set the runtime location to the EXE directory. - if (sdv::app::CAppControl::GetFrameworkRuntimeDirectory().empty()) - sdv::app::CAppControl::SetFrameworkRuntimeDirectory(GetExecDirectory()); - if (sdv::app::CAppControl::GetComponentInstallDirectory().empty()) - sdv::app::CAppControl::SetComponentInstallDirectory(GetExecDirectory()); - // Process the command line. CSdvPackagerEnvironment environment(iArgc, rgszArgv); @@ -54,7 +48,7 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) if (!environment.Silent()) { std::cout << "SDV Component Installation Package Utility" << std::endl; - std::cout << "Copyright (C): 2022-2025 ZF Friedrichshafen AG" << std::endl; + std::cout << "Copyright (C): 2022-2026 ZF Friedrichshafen AG" << std::endl; std::cout << "Author: Erik Verhoeven" << std::endl << std::endl; } @@ -114,7 +108,7 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) } // If running as server, load the settings file - if (!environment.Local() && !GetAppSettings().LoadSettingsFile()) + if (!environment.Local() && !GetAppSettings().LoadSettings()) { std::cerr << "ERROR: Failed to load the application settings file; cannot continue!" << std::endl; return -1; diff --git a/sdv_executables/sdv_packager/packager.cpp b/sdv_executables/sdv_packager/packager.cpp index de03560..f0fb875 100644 --- a/sdv_executables/sdv_packager/packager.cpp +++ b/sdv_executables/sdv_packager/packager.cpp @@ -450,7 +450,7 @@ bool CPackager::Configure() m_ssArgError = CMDLN_MISSING_TARGET_MSG; return false; } - if (!GetAppSettings().SaveSettingsFile()) + if (!GetAppSettings().SaveSettings()) { SDV_LOG_ERROR("Failed to save application settings. Cannot configure components!"); return false; @@ -743,7 +743,7 @@ bool CPackager::ConfigureFromManifest(const CInstallManifest& rmanifest) auto pathConfig = GetAppSettings().GetConfigPath(CAppSettings::EConfigType::user_config); WriteConfig(vecClasses, pathConfig, vecComponentsToAdd, true, vecAddedToConfig); } - if (!GetAppSettings().SaveSettingsFile()) + if (!GetAppSettings().SaveSettings()) { SDV_LOG_ERROR("Failed to save application settings. Cannot configure components!"); return false; @@ -806,11 +806,11 @@ void CPackager::WriteConfig(const std::vector& rvecAllClasses, break; case sdv::EObjectType::complex_service: case sdv::EObjectType::vehicle_function: - case sdv::EObjectType::utility: eIncompatible = bUserConfig ? ECompatibility::compatible : ECompatibility::incompatible; break; case sdv::EObjectType::proxy: case sdv::EObjectType::stub: + case sdv::EObjectType::utility: eIncompatible = ECompatibility::silent_incompatible; break; default: diff --git a/sdv_executables/sdv_trace_mon/main.cpp b/sdv_executables/sdv_trace_mon/main.cpp index 0fac0e3..bc276cd 100644 --- a/sdv_executables/sdv_trace_mon/main.cpp +++ b/sdv_executables/sdv_trace_mon/main.cpp @@ -65,7 +65,7 @@ static BOOL WINAPI ControlHandler(DWORD dwCtrlType) } #elif defined __unix__ /** - * @brief Signal handler to catch signal eevents. + * @brief Signal handler to catch signal events. * @param[in] iSigNum The signal type that was triggered. */ static void SignalHandler(int iSigNum) @@ -97,12 +97,6 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) if (thread.joinable()) thread.join(); - // If not set, set the runtime location to the EXE directory. - if (sdv::app::CAppControl::GetFrameworkRuntimeDirectory().empty()) - sdv::app::CAppControl::SetFrameworkRuntimeDirectory(GetExecDirectory()); - if (sdv::app::CAppControl::GetComponentInstallDirectory().empty()) - sdv::app::CAppControl::SetComponentInstallDirectory(GetExecDirectory()); - CCommandLine cmdln(static_cast(CCommandLine::EParseFlags::no_assignment_character)); bool bHelp = false; bool bVersion = false; @@ -123,7 +117,7 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) } std::cout << "SDV Server Log Monitor Utility" << std::endl; - std::cout << "Copyright (C): 2022-2025 ZF Friedrichshafen AG" << std::endl; + std::cout << "Copyright (C): 2022-2026 ZF Friedrichshafen AG" << std::endl; std::cout << "Author: Erik Verhoeven" << std::endl << std::endl; if (bVersion) diff --git a/sdv_executables/sdv_vss_util/main.cpp b/sdv_executables/sdv_vss_util/main.cpp index 9785294..5422f1b 100644 --- a/sdv_executables/sdv_vss_util/main.cpp +++ b/sdv_executables/sdv_vss_util/main.cpp @@ -109,7 +109,7 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) if (!bSilent) { std::cout << "abtract device / basic services utility" << std::endl; - std::cout << "Copyright (C): 2022-2025 ZF Friedrichshafen AG" << std::endl; + std::cout << "Copyright (C): 2022-2026 ZF Friedrichshafen AG" << std::endl; std::cout << "Author: Thomas Pfleiderer" << std::endl; } diff --git a/sdv_services/CMakeLists.txt b/sdv_services/CMakeLists.txt index ba5db0a..5924c17 100644 --- a/sdv_services/CMakeLists.txt +++ b/sdv_services/CMakeLists.txt @@ -170,6 +170,14 @@ add_custom_command( COMMAND sdv_idl_compiler ${INTERFACE_DIR}/repository.idl -O${INTERFACE_DIR} VERBATIM ) +add_custom_command( + OUTPUT ${INTERFACE_DIR}/permission.h + DEPENDS sdv_idl_compiler + MAIN_DEPENDENCY ${INTERFACE_DIR}/permission.idl + COMMENT "Compiling permission.idl" + COMMAND sdv_idl_compiler ${INTERFACE_DIR}/permission.idl -O${INTERFACE_DIR} --no_ps + VERBATIM + ) add_custom_command( OUTPUT ${INTERFACE_DIR}/timer.h DEPENDS sdv_idl_compiler @@ -198,6 +206,7 @@ add_custom_target(CompileCoreIDL ${INTERFACE_DIR}/process.h ${INTERFACE_DIR}/param.h ${INTERFACE_DIR}/repository.h + ${INTERFACE_DIR}/permission.h ${INTERFACE_DIR}/timer.h ) @@ -212,10 +221,14 @@ add_subdirectory(task_timer) add_subdirectory(ipc_com) add_subdirectory(ipc_connect) add_subdirectory(ipc_shared_mem) -add_subdirectory(uds_unix_sockets) -add_subdirectory(uds_unix_tunnel) -add_subdirectory(uds_win_sockets) -add_subdirectory(uds_win_tunnel) +if(UNIX) + add_subdirectory(uds_unix_sockets) + add_subdirectory(uds_unix_tunnel) +endif() +if(WIN32) + add_subdirectory(uds_win_sockets) + add_subdirectory(uds_win_tunnel) +endif() add_subdirectory(process_control) add_subdirectory(hardware_ident) add_subdirectory(manifest_util) diff --git a/sdv_services/can_communication_sim/can_com_sim.h b/sdv_services/can_communication_sim/can_com_sim.h index 2496b49..dc8e261 100644 --- a/sdv_services/can_communication_sim/can_com_sim.h +++ b/sdv_services/can_communication_sim/can_com_sim.h @@ -118,7 +118,7 @@ private: */ void PlaybackFunc(const asc::SCanMessage& rsMsg); - std::thread m_threadReceive; ///< Receive thread. + sdv::core::secure_thread m_threadReceive; ///< Receive thread. mutable std::mutex m_mtxReceivers; ///< Protect the receiver set. std::set m_setReceivers; ///< Set with receiver interfaces. mutable std::mutex m_mtxInterfaces; ///< Protect the nodes set. diff --git a/sdv_services/can_communication_socket_can/can_com_sockets.cpp b/sdv_services/can_communication_socket_can/can_com_sockets.cpp index ec17be0..02319a3 100644 --- a/sdv_services/can_communication_socket_can/can_com_sockets.cpp +++ b/sdv_services/can_communication_socket_can/can_com_sockets.cpp @@ -64,7 +64,7 @@ bool CCANSockets::OnInitialize() } LogConfigurations(); - m_threadReceive = std::thread(&CCANSockets::ReceiveThreadFunc, this); + m_threadReceive = sdv::core::secure_thread(&CCANSockets::ReceiveThreadFunc, this); return true; } diff --git a/sdv_services/can_communication_socket_can/can_com_sockets.h b/sdv_services/can_communication_socket_can/can_com_sockets.h index 18381bd..6717686 100644 --- a/sdv_services/can_communication_socket_can/can_com_sockets.h +++ b/sdv_services/can_communication_socket_can/can_com_sockets.h @@ -146,7 +146,7 @@ private: std::string name; ///< interface name, can be empty in case of an invalid socket element }; - std::thread m_threadReceive; ///< Receive thread. + sdv::core::secure_thread m_threadReceive; ///< Receive thread. mutable std::mutex m_mtxReceivers; ///< Protect the receiver set. std::set m_setReceivers; ///< Set with receiver interfaces. mutable std::mutex m_mtxSockets; ///< Protect the socket list. diff --git a/sdv_services/core/CMakeLists.txt b/sdv_services/core/CMakeLists.txt index 37ce962..70006e6 100644 --- a/sdv_services/core/CMakeLists.txt +++ b/sdv_services/core/CMakeLists.txt @@ -49,13 +49,14 @@ add_library(core_services SHARED "toml_parser_util.h" "installation_manifest.h" "installation_manifest.cpp" + "../../global/tracefifo/trace_fifo.cpp" "local_shutdown_request.h" "iso_monitor.h" "iso_monitor.cpp" "installation_composer.h" "installation_composer.cpp" - "toml_parser/lexer_toml_token.h" "toml_parser/lexer_toml_token.cpp" "toml_parser/miscellaneous.h" "toml_parser/miscellaneous.cpp" "toml_parser/code_snippet.h" "toml_parser/code_snippet.cpp" "app_settings.h" "app_settings.cpp" "app_config_file.h" "app_config_file.cpp") + "toml_parser/lexer_toml_token.h" "toml_parser/lexer_toml_token.cpp" "toml_parser/miscellaneous.h" "toml_parser/miscellaneous.cpp" "toml_parser/code_snippet.h" "toml_parser/code_snippet.cpp" "app_settings.h" "app_settings.cpp" "app_config_file.h" "app_config_file.cpp" "toml_parser/parser_node_indexer.h" "toml_parser/parser_node_indexer.cpp" "permission_control.h" "permission_control.cpp") # Compiler settiings add_compile_definitions(SDV_NO_EXPORT_DEFINITION) diff --git a/sdv_services/core/app_config.cpp b/sdv_services/core/app_config.cpp index 7cb921a..4a3c398 100644 --- a/sdv_services/core/app_config.cpp +++ b/sdv_services/core/app_config.cpp @@ -33,61 +33,6 @@ CAppConfig& GetAppConfig() return app_config; } -// GetCoreDirectory might have been redirected for unit tests. -#ifndef GetCoreDirectory -std::filesystem::path GetCoreDirectory() -{ - static std::filesystem::path pathCoreDir; - if (!pathCoreDir.empty()) - return pathCoreDir; - -#ifdef _WIN32 - // Windows specific - std::wstring ssPath(32768, '\0'); - - MEMORY_BASIC_INFORMATION sMemInfo{}; - if (!VirtualQuery(&pathCoreDir, &sMemInfo, sizeof(sMemInfo))) - return pathCoreDir; - DWORD dwLength = GetModuleFileNameW(reinterpret_cast(sMemInfo.AllocationBase), ssPath.data(), 32767); - ssPath.resize(dwLength); - pathCoreDir = std::filesystem::path(ssPath); - return pathCoreDir.remove_filename(); -#elif __linux__ - // Read the maps file. It contains all loaded SOs. - std::ifstream fstream("/proc/self/maps"); - std::stringstream sstreamMap; - sstreamMap << fstream.rdbuf(); - std::string ssMap = sstreamMap.str(); - if (ssMap.empty()) - return pathCoreDir; // Some error - - // Find the "core_services.sdv" - size_t nPos = ssMap.find("core_services.sdv"); - if (nPos == std::string::npos) - return pathCoreDir; - size_t nEnd = nPos; - - // Find the start... runbackwards until the beginning of the line and remember the earliest occurance of a slash - size_t nBegin = 0; - while (nPos && ssMap[nPos] != '\n') - { - if (ssMap[nPos] == '/') - nBegin = nPos; - nPos--; - } - if (!nBegin) - nBegin = nPos; - - // Return the path - pathCoreDir = ssMap.substr(nBegin, nEnd - nBegin); - - return pathCoreDir; -#else - #error The OS is not supported! -#endif -} -#endif // !defined GetCoreDirectory - bool CAppConfig::LoadInstallationManifests() { // Check for allowance @@ -96,13 +41,14 @@ bool CAppConfig::LoadInstallationManifests() { case sdv::app::EAppContext::main: case sdv::app::EAppContext::isolated: + case sdv::app::EAppContext::external: case sdv::app::EAppContext::maintenance: bServerApp = true; default: break; } - std::filesystem::path pathCore = GetCoreDirectory(); + std::filesystem::path pathCore = GetAppSettings().GetFrameworkDir(); std::filesystem::path pathExe = GetExecDirectory(); std::filesystem::path pathInstall = GetAppSettings().GetInstallDir(); @@ -155,8 +101,8 @@ void CAppConfig::UnloadInstallatonManifests() bool CAppConfig::LoadAppConfigs() { - // Isolated applications do not load configurations - if (GetAppSettings().IsIsolatedApplication()) return false; + // Isolated and external applications do not load configurations + if (GetAppSettings().IsIsolatedApplication() || GetAppSettings().IsExternalApplication()) return false; // When running as server application, load the system configurations if (GetAppSettings().IsMainApplication() || GetAppSettings().IsMaintenanceApplication()) @@ -243,6 +189,7 @@ sdv::core::EConfigProcessResult CAppConfig::ProcessConfig(/*in*/ const sdv::u8st { case sdv::app::EAppContext::main: case sdv::app::EAppContext::isolated: + case sdv::app::EAppContext::external: case sdv::app::EAppContext::maintenance: return sdv::core::EConfigProcessResult::failed; default: @@ -275,7 +222,7 @@ sdv::core::EConfigProcessResult CAppConfig::ProcessConfig(/*in*/ const sdv::u8st //size_t nLoadable = 0; //size_t nNotLoadable = 0; //if (!GetAppSettings().IsMainApplication() && !GetAppSettings().IsIsolatedApplication() && - // !GetAppSettings().IsMaintenanceApplication()) + // !GetAppSettings().IsExternalApplication() && !GetAppSettings().IsMaintenanceApplication()) //{ // // Load all modules in the component section // auto ptrComponents = parser.Root().Direct("Component"); @@ -394,7 +341,7 @@ sdv::core::EConfigProcessResult CAppConfig::ProcessConfig(/*in*/ const sdv::u8st // // loaded; get the module ID. // sdv::core::TObjectID tObjectID = 0; // if (!GetAppSettings().IsMainApplication() && !GetAppSettings().IsIsolatedApplication() && - // !GetAppSettings().IsMaintenanceApplication()) + // !GetAppSettings().IsExternalApplication() && !GetAppSettings().IsMaintenanceApplication()) // { // auto itModule = mapModules.find(pathModule); // if (itModule == mapModules.end()) continue; // Module was not loaded before... @@ -880,6 +827,7 @@ std::filesystem::path CAppConfig::FindInstalledModule(const std::filesystem::pat { case sdv::app::EAppContext::main: case sdv::app::EAppContext::isolated: + case sdv::app::EAppContext::external: case sdv::app::EAppContext::maintenance: bServerApp = true; default: @@ -914,6 +862,7 @@ std::string CAppConfig::FindInstalledModuleManifest(const std::filesystem::path& { case sdv::app::EAppContext::main: case sdv::app::EAppContext::isolated: + case sdv::app::EAppContext::external: case sdv::app::EAppContext::maintenance: bServerApp = true; default: @@ -948,6 +897,7 @@ std::optional CAppConfig::FindInstalledComponent(const std::str { case sdv::app::EAppContext::main: case sdv::app::EAppContext::isolated: + case sdv::app::EAppContext::external: case sdv::app::EAppContext::maintenance: bServerApp = true; default: @@ -980,6 +930,7 @@ bool CAppConfig::RemoveFromConfig(const CInstallManifest& /*rManifest*/) //{ //case sdv::app::EAppContext::main: //case sdv::app::EAppContext::isolated: + //case sdv::app::EAppContext::external: //case sdv::app::EAppContext::maintenance: // bServerApp = true; //default: @@ -1043,7 +994,7 @@ void CAppConfig::AddCurrentPath() if (!m_lstSearchPaths.empty()) return; // Add the core directory - std::filesystem::path pathCoreDir = GetCoreDirectory().lexically_normal(); + std::filesystem::path pathCoreDir = GetAppSettings().GetFrameworkDir().lexically_normal(); m_lstSearchPaths.push_back(pathCoreDir / "config/"); // Add the exe dir diff --git a/sdv_services/core/app_config.h b/sdv_services/core/app_config.h index 11b3575..9ea79df 100644 --- a/sdv_services/core/app_config.h +++ b/sdv_services/core/app_config.h @@ -39,12 +39,6 @@ // The installation might need companion files to be installed in various relative sub-directories. // @endcond -/** - * @brief Get the location of the core_services.sdv. - * @return Path to the directory containing the loaded core directory. - */ -std::filesystem::path GetCoreDirectory(); - /** * @brief Application configuration service * @details In the configuration system objects, devices, basic services, complex services and apps are defined and will be started diff --git a/sdv_services/core/app_config_file.cpp b/sdv_services/core/app_config_file.cpp index 48d64bd..ffe520d 100644 --- a/sdv_services/core/app_config_file.cpp +++ b/sdv_services/core/app_config_file.cpp @@ -346,9 +346,9 @@ Version = )toml" + std::to_string(SDVFrameworkInterfaceVersion) + R"toml( // Modules leftover in the vector are added to the list auto vecModuleListCopy = m_vecModuleList; sdv::toml::CNodeCollection nodeModules = nodeConfig.GetDirect("Module"); - for (size_t nIndex = nodeModules.GetCount() - 1; nIndex < nodeModules.GetCount(); --nIndex) + for (int64_t iIndex = static_cast(nodeModules.GetCount() - 1); iIndex >= 0; --iIndex) { - sdv::toml::CNodeCollection tableModule = nodeModules.Get(nIndex); + sdv::toml::CNodeCollection tableModule = nodeModules.Get(static_cast(iIndex)); std::filesystem::path pathModule = tableModule.GetDirect("Path").GetValue().get(); auto itModule = std::find_if(vecModuleListCopy.begin(), vecModuleListCopy.end(), [&](const SModule& rsModule) { return pathModule == rsModule.pathModule; }); @@ -374,9 +374,9 @@ Version = )toml" + std::to_string(SDVFrameworkInterfaceVersion) + R"toml( // Classes leftover in the vector are added to the list auto vecClassListCopy = m_vecClassList; sdv::toml::CNodeCollection arrayClasses = nodeConfig.GetDirect("Class"); - for (size_t nIndex = arrayClasses.GetCount() -1; nIndex < arrayClasses.GetCount(); --nIndex) + for (int64_t iIndex = static_cast(arrayClasses.GetCount() - 1); iIndex >= 0; --iIndex) { - sdv::toml::CNodeCollection tableClass = arrayClasses.Get(nIndex); + sdv::toml::CNodeCollection tableClass = arrayClasses.Get(static_cast(iIndex)); std::filesystem::path pathModule; if (!bServerApp) pathModule = tableClass.GetDirect("Path").GetValue().get(); @@ -419,8 +419,7 @@ Version = )toml" + std::to_string(SDVFrameworkInterfaceVersion) + R"toml( if (ssExistingTOML != itClass->ssDefaultConfig) { if (tableParams) tableParams.Delete(); - if (!itClass->ssDefaultConfig.empty()) - tableClass.InsertTOML(sdv::toml::npos, itClass->ssDefaultConfig); + if (!itClass->ssDefaultConfig.empty()) tableClass.InsertTOML("", itClass->ssDefaultConfig); rbChanged = true; } @@ -433,8 +432,7 @@ Version = )toml" + std::to_string(SDVFrameworkInterfaceVersion) + R"toml( tableClass.AddValue("Class", rsClass.ssName); if (!bServerApp) tableClass.AddValue("Path", std::filesystem::u8path(static_cast(rsClass.ssModulePath))); - if (!rsClass.ssDefaultConfig.empty()) - tableClass.InsertTOML(sdv::toml::npos, rsClass.ssDefaultConfig); + if (!rsClass.ssDefaultConfig.empty()) tableClass.InsertTOML("", rsClass.ssDefaultConfig); rbChanged = true; } @@ -444,9 +442,9 @@ Version = )toml" + std::to_string(SDVFrameworkInterfaceVersion) + R"toml( // Components leftover in the vector are added to the list auto vecComponentListCopy = m_vecComponentList; sdv::toml::CNodeCollection nodeComponents = nodeConfig.GetDirect("Component"); - for (size_t nIndex = nodeComponents.GetCount() - 1; nIndex < nodeComponents.GetCount(); --nIndex) + for (int64_t iIndex = static_cast(nodeComponents.GetCount() - 1); iIndex >= 0; --iIndex) { - sdv::toml::CNodeCollection tableComponent = nodeComponents.Get(nIndex); + sdv::toml::CNodeCollection tableComponent = nodeComponents.Get(static_cast(iIndex)); std::filesystem::path pathModule; if (!bServerApp) pathModule = tableComponent.GetDirect("Path").GetValue().get(); @@ -518,8 +516,8 @@ Version = )toml" + std::to_string(SDVFrameworkInterfaceVersion) + R"toml( else if (!tableParams) { // Simply add the parameters - tableParams = tableComponent.InsertTable(sdv::toml::npos, "Parameters"); - tableParams.InsertTOML(sdv::toml::npos, itComponent->ssParameterTOML); + tableParams = tableComponent.InsertTable("", "Parameters"); + tableParams.InsertTOML("", itComponent->ssParameterTOML); rbChanged = true; } } @@ -536,8 +534,8 @@ Version = )toml" + std::to_string(SDVFrameworkInterfaceVersion) + R"toml( tableComponent.AddValue("Name", rsComponent.ssInstanceName); if (!rsComponent.ssParameterTOML.empty()) { - sdv::toml::CNodeCollection tableParams = tableComponent.InsertTable(sdv::toml::npos, "Parameters"); - tableParams.InsertTOML(sdv::toml::npos, rsComponent.ssParameterTOML); + sdv::toml::CNodeCollection tableParams = tableComponent.InsertTable("", "Parameters"); + tableParams.InsertTOML("", rsComponent.ssParameterTOML); } rbChanged = true; } @@ -628,12 +626,12 @@ bool CAppConfigFile::InsertComponent(size_t nIndex, const std::filesystem::path& // Need to add a group? if (prKey.first != ssGroup) { - group = root.InsertTable(sdv::toml::npos, prKey.first); + group = root.InsertTable("", prKey.first); ssGroup = prKey.first; } // Add the parameter - group.InsertValue(sdv::toml::npos, prKey.second, prParameter.second); + group.InsertValue("", prKey.second, prParameter.second); } ssParameterTOML = parser.GenerateTOML(); } diff --git a/sdv_services/core/app_config_file.h b/sdv_services/core/app_config_file.h index 02e6329..1e13a2f 100644 --- a/sdv_services/core/app_config_file.h +++ b/sdv_services/core/app_config_file.h @@ -266,6 +266,9 @@ public: */ void RemoveModule(const std::filesystem::path& rpathModule); + /** + * @brief Merge result enum. + */ enum class EMergeResult { successful, diff --git a/sdv_services/core/app_control.cpp b/sdv_services/core/app_control.cpp index dfd32fe..1ea881b 100644 --- a/sdv_services/core/app_control.cpp +++ b/sdv_services/core/app_control.cpp @@ -15,7 +15,6 @@ #include "module_control.h" #include "repository.h" #include "../../global/base64.h" -#include "../../global/tracefifo/trace_fifo.cpp" #include "toml_parser/parser_toml.h" #include "local_shutdown_request.h" #include "app_settings.h" @@ -49,6 +48,9 @@ bool CAppControl::Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfac { m_pEvent = pEventHandler ? pEventHandler->GetInterface() : nullptr; + // Allow this thread full access + m_optpermission = GetPermissionControl().CreatePermissionObject(sdv::core::EAccessPermission::full_access); + // Intercept the logging... std::stringstream sstreamCOUT, sstreamCLOG, sstreamCERR; std::streambuf* pstreambufCOUT = std::cout.rdbuf(sstreamCOUT.rdbuf()); @@ -86,7 +88,7 @@ bool CAppControl::Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfac } // Open the stream buffer and attach the streams if the application control is initialized as main app. - if (GetAppSettings().IsMainApplication()) + if (!GetAppSettings().RedirectMonitorToConsole() && GetAppSettings().IsMainApplication()) { m_fifoTraceStreamBuffer.SetInstanceID(GetAppSettings().GetInstanceID()); m_fifoTraceStreamBuffer.Open(1000); @@ -99,7 +101,7 @@ bool CAppControl::Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfac std::cerr << sstreamCERR.str(); // Check for a correctly opened stream buffer - if (GetAppSettings().IsMainApplication() && !m_fifoTraceStreamBuffer.IsOpened()) + if (!GetAppSettings().RedirectMonitorToConsole() && GetAppSettings().IsMainApplication() && !m_fifoTraceStreamBuffer.IsOpened()) { if (!GetAppSettings().IsConsoleSilent()) std::cerr << "ERROR: Log streaming could not be initialized; cannot continue!" << std::endl; @@ -142,7 +144,7 @@ bool CAppControl::Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfac }; auto fnCreateObject = [&ssErrorString](const sdv::u8string& rssClass, const sdv::u8string& rssObject, const sdv::u8string& rssConfig) -> bool { - bool bLocalRet = GetRepository().CreateObject2(rssClass, rssObject, rssConfig); + bool bLocalRet = GetRepository().CreateObject(rssClass, rssObject, rssConfig); if (!bLocalRet) { ssErrorString = std::string("Failed to instantiate a new object from class '") + rssClass + "'"; @@ -179,7 +181,7 @@ bool CAppControl::Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfac sdv::IInterfaceAccess* pLoggerObj = GetRepository().GetObject(GetAppSettings().GetLoggerClass()); if (!pLoggerObj) { - GetRepository().DestroyObject2(GetAppSettings().GetLoggerClass()); + GetRepository().DestroyObject(GetAppSettings().GetLoggerClass()); if (!GetAppSettings().IsConsoleSilent()) std::cerr << "ERROR: Failed to start the logger. Cannot continue!" << std::endl; Shutdown(true); @@ -247,7 +249,7 @@ bool CAppControl::Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfac } // Load the application settings. - if (!GetAppSettings().LoadSettingsFile()) + if (!GetAppSettings().LoadSettings()) { if (!GetAppSettings().IsConsoleSilent()) std::cerr << "ERROR: Failed to load application settings file. Cannot continue!" << std::endl; @@ -275,32 +277,88 @@ bool CAppControl::Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfac bRet = fnLoadModule("hardware_ident.sdv") ? true : false; if (bRet) bRet = fnCreateObject("HardwareIdentificationService", "", ""); - // Load shared memory channel - if (bRet) bRet = fnLoadModule("ipc_shared_mem.sdv") ? true : false; - if (bRet) bRet = fnCreateObject("DefaultSharedMemoryChannelControl", "", ""); + // Load default communication provider + if (bRet) bRet = fnCreateObject(GetAppSettings().GetDefaultComProvider(), "", ""); // Load IPC service if (bRet) bRet = fnLoadModule("ipc_com.sdv") ? true : false; if (bRet) bRet = fnCreateObject("CommunicationControl", "", ""); - // Load IPC service and create listener local connections - if (bRet) bRet = fnLoadModule("ipc_listener.sdv") ? true : false; - if (bRet) bRet = fnCreateObject("ConnectionListenerService", "", R"code([Listener] -Type = "Local" -)code"); - - // Load IPC service - if (bRet) bRet = fnLoadModule("ipc_connect.sdv") ? true : false; - if (bRet) bRet = fnCreateObject("ConnectionService", "", ""); - // Load proxy/stub for core interfaces if (bRet) bRet = fnLoadModule("core_ps.sdv") ? true : false; -// // Start the listener -// if (bRet) bRet = fnLoadModule("ipc_listener.sdv") ? true : false; -// if (bRet) bRet = GetRepository().CreateObject("ConnectionListenerService", "ConnectionListenerService", R"code([Listener] -//Type="local" -//Instance=)code" + std::to_string(GetInstanceID())); + // Load the connection services + if (bRet) bRet = fnLoadModule("ipc_listener.sdv") ? true : false; + if (bRet) bRet = fnLoadModule("ipc_connect.sdv") ? true : false; + + // For the main application start the configured listeners. + if (bRet) + { + auto seqListeners = GetAppSettings().GetListeners(); + for (const auto& rssListener : seqListeners) + { + if (!bRet) break; + auto ssListenerConfig = GetAppSettings().GetListenerConfig(rssListener); + bRet = fnCreateObject("ListenerConnectService", "Listener_" + rssListener, ssListenerConfig); + } + } + + // For the main application connect the configured connections (except default connection). + if (bRet) + { + auto seqConnections = GetAppSettings().GetConnections(); + for (const auto& rssConnection : seqConnections) + { + if (!bRet) break; + if (rssConnection == "Default") continue; + + if (!GetAppSettings().IsConsoleSilent()) + std::cout << "INFO: Trying to connect to " << rssConnection << "..." << std::endl; + + auto ssConnectionConfig = GetAppSettings().GetConnectionConfig(rssConnection); + std::string ssObjectName = "Client_" + rssConnection; + bRet = fnCreateObject("ClientConnectService", ssObjectName, ssConnectionConfig); + + // Try connect (for at least nTries) and when successful bind repositories + sdv::TInterfaceAccessPtr ptrClient = GetRepository().GetObject(ssObjectName); + sdv::com::IClientConnect* pClientConnect = ptrClient.GetInterface(); + if (pClientConnect) + { + for (uint32_t uiCnt = 0; uiCnt < GetAppSettings().GetConnectRetries(); uiCnt++) + { + // Try to connect. If not working, wait for 300 ms + if (pClientConnect->Connect()) break; + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + } + + if (pClientConnect->IsConnected()) + { + // Register the core repo as repo access + sdv::core::TLinkID tLinkID = GetRepository().LinkCoreRepository(pClientConnect->GetRemoteRepository()); + + if (!GetAppSettings().IsConsoleSilent()) + std::cout << "INFO: Connection established... " << std::endl; + + // Store the connection object. + m_vecConnections.push_back(std::make_pair(ssObjectName, tLinkID)); + } + else + { + ssErrorString = "Not connected..."; + if (!GetAppSettings().IsConsoleSilent()) + std::cout << "ERROR: Failed to connect to " << ssConnectionConfig << std::endl; + bRet = false; + } + } + else + { + ssErrorString = "No client connect interface..."; + if (!GetAppSettings().IsConsoleSilent()) + std::cerr << "ERROR: Cannot create connection object for " << rssConnection << std::endl; + bRet = false; + } + } + } if (!bRet) { @@ -314,25 +372,69 @@ Type = "Local" } else if (bRet && bLoadRPCClient) { - // Interpret the connect string... - - // Load hardware identification bRet = fnLoadModule("hardware_ident.sdv") ? true : false; if (bRet) bRet = fnCreateObject("HardwareIdentificationService", "", ""); - // Load shared memory channel - if (bRet) bRet = fnLoadModule("ipc_shared_mem.sdv") ? true : false; - if (bRet) bRet = fnCreateObject("DefaultSharedMemoryChannelControl", "", ""); + // Load default communication provider + if (bRet) bRet = fnCreateObject(GetAppSettings().GetDefaultComProvider(), "", ""); // Load IPC service if (bRet) bRet = fnLoadModule("ipc_com.sdv") ? true : false; if (bRet) bRet = fnCreateObject("CommunicationControl", "", ""); - // Load IPC service - if (bRet) bRet = fnLoadModule("ipc_connect.sdv") ? true : false; - if (bRet) bRet = fnCreateObject("ConnectionService", "", ""); + // Connect to the core system when running as external aplication. Use the default connection for this. + if (bRet && GetAppSettings().GetContextType() == sdv::app::EAppContext::external) + { + // Load the connection services + bRet = fnLoadModule("ipc_connect.sdv") ? true : false; + if (!GetAppSettings().IsConsoleSilent()) + std::cout << "INFO: Trying to connect to the core system..." << std::endl; + + // And the one connection + auto ssConnectionConfig = GetAppSettings().GetConnectionConfig("Default"); + if (bRet && !ssConnectionConfig.empty()) + bRet = fnCreateObject("ClientConnectService", "Client_Default", ssConnectionConfig); + + // Try connect (for at least nTries) and when successful bind repositories + sdv::TInterfaceAccessPtr ptrClient = GetRepository().GetObject("Client_Default"); + sdv::com::IClientConnect* pClientConnect = ptrClient.GetInterface(); + if (pClientConnect) + { + for (uint32_t uiCnt = 0; uiCnt < GetAppSettings().GetConnectRetries(); uiCnt++) + { + // Try to connect. If not working, wait for 300 ms + if (pClientConnect->Connect()) break; + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + } + + if (pClientConnect->IsConnected()) + { + // Register the core repo as repo access + sdv::core::TLinkID tLinkID = GetRepository().LinkCoreRepository(pClientConnect->GetRemoteRepository()); + + if (!GetAppSettings().IsConsoleSilent()) + std::cout << "INFO: Connection established... " << std::endl; + + // Store the connection object. + m_vecConnections.push_back(std::make_pair("Default", tLinkID)); + } + else + { + if (!GetAppSettings().IsConsoleSilent()) + std::cout << "ERROR: Failed to connect to the core system" << std::endl; + bRet = false; + } + } + else + { + if (!GetAppSettings().IsConsoleSilent()) + std::cerr << "ERROR: Cannot create connection object for connecting to the core system." << std::endl; + bRet = false; + } + } + // Load proxy/stub for core interfaces if (bRet) bRet = fnLoadModule("core_ps.sdv") ? true : false; @@ -428,6 +530,7 @@ void CAppControl::RunLoop() { case sdv::app::EAppContext::main: case sdv::app::EAppContext::isolated: + case sdv::app::EAppContext::external: bLocal = false; break; case sdv::app::EAppContext::maintenance: @@ -490,8 +593,22 @@ void CAppControl::Shutdown(/*in*/ bool bForce) // Disable automatic configuration saving. m_bAutoSaveConfig = false; - // Update the application settings file - GetAppSettings().SaveSettingsFile(); + // TODO EVE: This is likely not wanted + //// Update the application settings file + //GetAppSettings().SaveSettings(); + + // Disconnect all automatic connections. + for (const auto& rprConnectObject : m_vecConnections) + { + // If there is a registered link ID, unlink the ID. + if (rprConnectObject.second) + GetRepository().UnlinkCoreRepository(rprConnectObject.second); + + sdv::TInterfaceAccessPtr ptrClient = GetRepository().GetObject(rprConnectObject.first); + sdv::com::IClientConnect* pClientConnect = ptrClient.GetInterface(); + if (pClientConnect) pClientConnect->Disconnect(); + } + m_vecConnections.clear(); // Destroy all objects... this should also remove any registered services, except the custom logger. GetRepository().DestroyAllObjects(std::vector({GetAppSettings().GetLoggerClass()}), bForce); @@ -537,7 +654,7 @@ void CAppControl::Shutdown(/*in*/ bool bForce) } // End trace streaming - if (GetAppSettings().IsMainApplication()) + if (!GetAppSettings().RedirectMonitorToConsole() && GetAppSettings().IsMainApplication()) { std::cout << "**********************************************" << std::endl; @@ -551,6 +668,7 @@ void CAppControl::Shutdown(/*in*/ bool bForce) m_bAutoSaveConfig = false; m_bEnableAutoSave = false; GetAppSettings().Reset(); + m_optpermission.reset(); } void CAppControl::RequestShutdown() diff --git a/sdv_services/core/app_control.h b/sdv_services/core/app_control.h index 39ed3af..0e8a032 100644 --- a/sdv_services/core/app_control.h +++ b/sdv_services/core/app_control.h @@ -18,6 +18,8 @@ #include #include #include "../../global/tracefifo/trace_fifo.h" +#include "permission_control.h" +#include /** * @brief Application control class. @@ -140,17 +142,20 @@ private: */ void BroadcastOperationState(sdv::app::EAppOperationState eState); - sdv::app::EAppOperationState m_eState = sdv::app::EAppOperationState::not_started; ///< The current operation state. - sdv::app::IAppEvent* m_pEvent = nullptr; ///< Pointer to the app event interface. - sdv::core::TModuleID m_tLoggerModuleID = 0; ///< ID of the logger module. - bool m_bEnableAutoSave = false; ///< When set and when enabled in the system settings, allows - ///< the automatic saving of the configuration. - bool m_bRunLoop = false; ///< Used to detect end of running loop function. - std::filesystem::path m_pathLockFile; ///< Lock file path name. - FILE* m_pLockFile = nullptr; ///< Lock file to test for other instances. - CTraceFifoStdBuffer m_fifoTraceStreamBuffer; ///< Trace stream buffer to redirect std::log, std::out and - ///< std::err when running as service. - bool m_bAutoSaveConfig = false; ///< System setting for automatic saving of the user configuration. + sdv::app::EAppOperationState m_eState = sdv::app::EAppOperationState::not_started; ///< The current operation state. + sdv::app::IAppEvent* m_pEvent = nullptr; ///< Pointer to the app event interface. + sdv::core::TModuleID m_tLoggerModuleID = 0; ///< ID of the logger module. + bool m_bEnableAutoSave = false; ///< When set and when enabled in the system settings, allows + ///< the automatic saving of the configuration. + bool m_bRunLoop = false; ///< Used to detect end of running loop function. + std::filesystem::path m_pathLockFile; ///< Lock file path name. + FILE* m_pLockFile = nullptr; ///< Lock file to test for other instances. + CTraceFifoStdBuffer m_fifoTraceStreamBuffer; ///< Trace stream buffer to redirect std::log, std::out and + ///< std::err when running as service. + bool m_bAutoSaveConfig = false; ///< System setting for automatic saving of the user configuration. + std::vector> m_vecConnections; ///< Connection objects that were successfully + ///< connected. + std::optional m_optpermission; ///< Main access permission for the core. }; /** diff --git a/sdv_services/core/app_settings.cpp b/sdv_services/core/app_settings.cpp index ddf69d2..fb2a7b6 100644 --- a/sdv_services/core/app_settings.cpp +++ b/sdv_services/core/app_settings.cpp @@ -17,252 +17,12 @@ #include "../../global/exec_dir_helper.h" #include "../../global/ipc_named_mutex.h" #include +#include -CAppSettings& GetAppSettings() -{ - static CAppSettings app_settings; - return app_settings; -} - -CAppSettings::CAppSettings() -{} - -CAppSettings::~CAppSettings() -{} - -bool CAppSettings::ProcessAppStartupConfig(const sdv::u8string& rssConfig) -{ - toml_parser::CParser parserStartupConfig; - std::string ssError; - try - { - // Read the configuration - if (!parserStartupConfig.Process(rssConfig)) - return false; - } - catch (const sdv::toml::XTOMLParseException& rexcept) - { - ssError = std::string("ERROR: Failed to parse application configuration: ") + rexcept.what(); - } - - sdv::toml::CNodeCollection tableStartupConfig(&parserStartupConfig.Root()); - - // Get the reporting settings (if this succeeded at all...) - m_bSilent = tableStartupConfig.GetDirect("Console.Report").GetValue().get() == "Silent"; - m_bVerbose = tableStartupConfig.GetDirect("Console.Report").GetValue().get() == "Verbose"; - - // Report the outstanding error (if there is one...) - if (!ssError.empty()) - { - if (!m_bSilent) - std::cerr << ssError << std::endl; - return false; - } - - // Allow a custom logger to be defined - m_pathLoggerModule = tableStartupConfig.GetDirect("LogHandler.Path").GetValue().get(); - m_ssLoggerClass = tableStartupConfig.GetDirect("LogHandler.Class").GetValue().get(); - if (m_ssLoggerClass.empty()) - m_ssLoggerClass = "DefaultLoggerService"; - - // Get an optional program tag for the logger - m_ssProgramTag = tableStartupConfig.GetDirect("LogHandler.Tag").GetValue().get(); - - // Get the application-mode - std::string ssApplication = tableStartupConfig.GetDirect("Application.Mode").GetValue(); - if (ssApplication.empty()) ssApplication = "Standalone"; - if (ssApplication == "Standalone") - m_eContextMode = sdv::app::EAppContext::standalone; - else if (ssApplication == "External") - m_eContextMode = sdv::app::EAppContext::external; - else if (ssApplication == "Isolated") - m_eContextMode = sdv::app::EAppContext::isolated; - else if (ssApplication == "Main") - m_eContextMode = sdv::app::EAppContext::main; - else if (ssApplication == "Essential") - m_eContextMode = sdv::app::EAppContext::essential; - else if (ssApplication == "Maintenance") - m_eContextMode = sdv::app::EAppContext::maintenance; - else - { - if (!m_bSilent) - std::cerr << "ERROR: Failed to process startup config: invalid application-mode specified for core library: " - << ssApplication << std::endl; - return false; - } - - // Get the severity level filter for the logger - auto fnTranslateSevFilter = [this](const std::string& rssLogFilter, const sdv::core::ELogSeverity eDefault) - { - sdv::core::ELogSeverity eSeverityFilter = eDefault; - if (rssLogFilter == "Trace") - eSeverityFilter = sdv::core::ELogSeverity::trace; - else if (rssLogFilter == "Debug") - eSeverityFilter = sdv::core::ELogSeverity::debug; - else if (rssLogFilter == "Info") - eSeverityFilter = sdv::core::ELogSeverity::info; - else if (rssLogFilter == "Warning") - eSeverityFilter = sdv::core::ELogSeverity::warning; - else if (rssLogFilter == "Error") - eSeverityFilter = sdv::core::ELogSeverity::error; - else if (rssLogFilter == "Fatal") - eSeverityFilter = sdv::core::ELogSeverity::fatal; - else if (!rssLogFilter.empty()) - { - if (!m_bSilent) - std::cerr << "ERROR: Failed to process application log: invalid severity level filter: '" << rssLogFilter << "'" - << std::endl; - } - return eSeverityFilter; - }; - sdv::core::ELogSeverity eLogDefaultViewSeverityFilter = sdv::core::ELogSeverity::error; - if (IsMainApplication() || IsIsolatedApplication()) eLogDefaultViewSeverityFilter = sdv::core::ELogSeverity::info; - m_eSeverityFilter = fnTranslateSevFilter(tableStartupConfig.GetDirect("LogHandler.Filter").GetValue(), - sdv::core::ELogSeverity::info); - m_eSeverityViewFilter = fnTranslateSevFilter( - tableStartupConfig.GetDirect("LogHandler.ViewFilter").GetValue(), eLogDefaultViewSeverityFilter); - - // Get the optional instance ID. - sdv::any_t anyInstanceID = tableStartupConfig.GetDirect("Application.Instance").GetValue(); - if (anyInstanceID) m_uiInstanceID = anyInstanceID; - else - m_uiInstanceID = 1000u; - - // Number of attempts to establish a connection to a running instance. - m_uiRetries = tableStartupConfig.GetDirect("Application.Retries").GetValue(); - if (m_uiRetries > 30) - m_uiRetries = 30; - else if (m_uiRetries < 3) - m_uiRetries = 3; - - // Main and isolated apps specific information. - if (IsMainApplication() || IsIsolatedApplication() || IsMaintenanceApplication()) - { - // Get the optional installation directory. - m_pathRootDir = tableStartupConfig.GetDirect("Application.InstallDir").GetValueAsPath(); - if (!m_pathRootDir.empty()) - { - if (m_pathRootDir.is_relative()) - m_pathRootDir = GetExecDirectory() / m_pathRootDir; - } - else - m_pathRootDir = GetExecDirectory(); - m_pathInstallDir = m_pathRootDir / std::to_string(m_uiInstanceID); - try - { - std::filesystem::create_directories(m_pathInstallDir); - } - catch (const std::filesystem::filesystem_error& rexcept) - { - if (!m_bSilent) - { - std::cerr << "Cannot create installation directory: " << m_pathInstallDir << std::endl; - std::cerr << " Reason: " << rexcept.what() << std::endl; - } - return false; - } - } - - // Maintenance, main and isolated applications cannot load specific configs. The others can specify a configuration file, but - // not auto-updateable. - if (!IsMainApplication() && !IsMaintenanceApplication() && !IsIsolatedApplication()) - m_pathUserConfig = tableStartupConfig.GetDirect("Application.Config").GetValueAsPath(); - - // Read the settings... if existing. And only for the main application - if (IsMainApplication() && !LoadSettingsFile()) - return false; - - return true; -} - -bool CAppSettings::LoadSettingsFile() -{ - // Check for the proper context - switch (m_eContextMode) - { - case sdv::app::EAppContext::main: - case sdv::app::EAppContext::isolated: - case sdv::app::EAppContext::maintenance: - break; - default: - return true; // Not an error... - } - - // If the template is not existing, this is not an error... - if (!std::filesystem::exists(m_pathInstallDir / "settings.toml")) - return true; - - std::ifstream fstream(m_pathInstallDir / "settings.toml"); - std::string ssSettings((std::istreambuf_iterator(fstream)), std::istreambuf_iterator()); - fstream.close(); - - try - { - // Read the configuration - toml_parser::CParser parserSettings(ssSettings); - - // If there is no "Settings" table, this is not an error... - sdv::toml::CNodeCollection tableSettings(parserSettings.Root().GetNodeDirect("Settings")); - if (!tableSettings) return true; - - // Check for the version - uint32_t uiVersion = tableSettings.GetDirect("Version").GetValue(); - if (uiVersion != SDVFrameworkInterfaceVersion) - { - if (!m_bSilent) - std::cerr << "ERROR: Invalid version of application settings file (expected version " - << SDVFrameworkInterfaceVersion << ", but available version " << uiVersion << ")" << std::endl; - return false; - } - - // Get the platform config - but only when not specified over the app-control-config. - if (m_pathPlatformConfig.empty()) - m_pathPlatformConfig = tableSettings.GetDirect("PlatformConfig").GetValueAsPath(); - - // Get the vehicle interface config - but only when not specified over the app-control-config. - if (m_pathVehIfcConfig.empty()) - m_pathVehIfcConfig = tableSettings.GetDirect("VehIfcConfig").GetValueAsPath(); - - // Get the vehicle abstraction config - but only when not specified over the app-control-config. - if (m_pathVehAbstrConfig.empty()) - m_pathVehAbstrConfig = tableSettings.GetDirect("VehAbstrConfig").GetValueAsPath(); - - // Get the application config - but only when not specified over the app-control-config. - if (m_pathUserConfig.empty()) - m_pathUserConfig = tableSettings.GetDirect("AppConfig").GetValueAsPath(); - } - catch (const sdv::toml::XTOMLParseException& rexcept) - { - if (!m_bSilent) - std::cerr << "ERROR: Failed to parse application settings: " << rexcept.what() << std::endl; - return false; - } - return true; -} - -bool CAppSettings::SaveSettingsFile() -{ - // Check for the proper context - switch (m_eContextMode) - { - case sdv::app::EAppContext::main: - case sdv::app::EAppContext::isolated: - case sdv::app::EAppContext::maintenance: - break; - default: - return true; // Not an error... - } - - // Protect against multiple write actions at the same time. - ipc::named_mutex mtx("LockSdvAppSettings_" + std::to_string(m_uiInstanceID)); - // Warning of cppcheck for locking a local mutex, which doesn't have any effect. Since this is a named mutex between - // applciations, the warning is not correct. Suppress warning. - // cppcheck-suppress localMutex - std::unique_lock lock(mtx); - - const std::string ssSettingsTemplate = R"toml(# Settings file +const std::string g_ssSettingsTemplate = R"toml(# Settings file [Settings] -Version = )toml" + std::to_string(SDVFrameworkInterfaceVersion) + R"toml( +Version = )toml" + std::to_string(SDVFrameworkInterfaceVersion) + + R"toml( # The system configuration is divided into: # platform config - containing all the components needed to interact with the OS, @@ -290,22 +50,533 @@ VehAbstrConfig = "" # config by providing the "AppConfig" keyword as a string value. A relative path is relative to # the installation directory (being "exe_location/instance_id"). # -# Example +# Example: # AppConfig = "app_config.toml" AppConfig = "" + +# A list of zero or more listener definitions that should be instantiated during startup of the +# main application. If no listener definition is available, the default shared-memory listener +# is being instantiated. +# +# Example: +# [[Settings.Listener]] +# Name = "SharedMemListener" +# [Settings.Listerner.Provider] +# Name = "DefaultSharedMemory" +# [Settings.Listener.IpcChannel] +# Name = "CHANNEL_1234" +# Size = 1024 +# +# A list of zero or more client connections that should be instantiated during startup. +# +# Example: +# [[Settings.Connection]] +# Name = "SharedMemConnection" +# [Settings.Connection.Provider] +# Name = "DefaultSharedMemory" +# [Settings.Listener.IpcChannel] +# Name = "CHANNEL_1234" + )toml"; + + +CAppSettings& GetAppSettings() +{ + static CAppSettings app_settings; + return app_settings; +} + +CAppSettings::CAppSettings() +{ +#ifdef _WIN32 + // Windows specific + std::wstring ssPath(32768, '\0'); + + MEMORY_BASIC_INFORMATION sMemInfo{}; + if (VirtualQuery(&m_pathFrameworkDir, &sMemInfo, sizeof(sMemInfo))) + { + DWORD dwLength = GetModuleFileNameW(reinterpret_cast(sMemInfo.AllocationBase), ssPath.data(), 32767); + ssPath.resize(dwLength); + m_pathFrameworkDir = std::filesystem::path(ssPath).remove_filename(); + } + +#elif __linux__ + // Read the maps file. It contains all loaded SOs. + std::ifstream fstream("/proc/self/maps"); + std::stringstream sstreamMap; + sstreamMap << fstream.rdbuf(); + std::string ssMap = sstreamMap.str(); + if (!ssMap.empty()) + { + // Find the "core_services.sdv" + size_t nPos = ssMap.find("core_services.sdv"); + if (nPos != std::string::npos) + { + size_t nEnd = nPos; + + // Find the start... runbackwards until the beginning of the line and remember the earliest occurance of a slash + size_t nBegin = 0; + while (nPos && ssMap[nPos] != '\n') + { + if (ssMap[nPos] == '/') nBegin = nPos; + nPos--; + } + if (!nBegin) nBegin = nPos; + + // Return the path + m_pathFrameworkDir = ssMap.substr(nBegin, nEnd - nBegin); + } +} +#else + #error The OS is not supported! +#endif +} + +CAppSettings::~CAppSettings() +{} + +bool CAppSettings::ProcessAppStartupConfig(const sdv::u8string& rssConfig) +{ + // Initialize the parameter map + InitParamMap(); + + // Process configuration + toml_parser::CParser parserStartupConfig; + std::string ssError; + try + { + // Read the configuration + if (!parserStartupConfig.Process(rssConfig)) + return false; + } + catch (const sdv::toml::XTOMLParseException& rexcept) + { + ssError = std::string("ERROR: Failed to parse application configuration: ") + rexcept.what(); + } + + sdv::toml::CNodeCollection tableStartupConfig(&parserStartupConfig.Root()); + + // Get the reporting settings (if this succeeded at all...) + std::string ssConsoleRerporting = tableStartupConfig.GetDirect("Console.Report").GetValueAsString(); + if (ssConsoleRerporting == "Silent") + m_eConsoleReporting = EAppConsoleReporting::silent; + if (ssConsoleRerporting == "Verbose") + m_eConsoleReporting = EAppConsoleReporting::verbose; + + // Get the application-mode + std::string ssApplication = tableStartupConfig.GetDirect("Application.Mode").GetValue(); + if (ssApplication.empty()) + ssApplication = "Standalone"; + if (ssApplication == "Standalone") + m_eAppContextType = sdv::app::EAppContext::standalone; + else if (ssApplication == "External") + m_eAppContextType = sdv::app::EAppContext::external; + else if (ssApplication == "Isolated") + m_eAppContextType = sdv::app::EAppContext::isolated; + else if (ssApplication == "Main") + m_eAppContextType = sdv::app::EAppContext::main; + else if (ssApplication == "Essential") + m_eAppContextType = sdv::app::EAppContext::essential; + else if (ssApplication == "Maintenance") + m_eAppContextType = sdv::app::EAppContext::maintenance; + else + { + if (!IsConsoleSilent()) + std::cerr << "ERROR: Failed to process startup config: invalid application-mode specified for core library: " + << ssApplication << std::endl; + return false; + } + + // Redirect monitoring? Only for main application + if (m_eAppContextType == sdv::app::EAppContext::main) + m_bRedirectMon = tableStartupConfig.GetDirect("Console.RedirectMon"); + + // Report the outstanding error (if there is one...) + if (!ssError.empty()) + { + if (!IsConsoleSilent()) + std::cerr << ssError << std::endl; + return false; + } + + // Allow a custom logger to be defined + m_pathLoggerModule = tableStartupConfig.GetDirect("LogHandler.Path").GetValue().get(); + m_ssLoggerClass = tableStartupConfig.GetDirect("LogHandler.Class").GetValue().get(); + if (m_ssLoggerClass.empty()) + m_ssLoggerClass = "DefaultLoggerService"; + + // Get an optional program tag for the logger + m_ssProgramTag = tableStartupConfig.GetDirect("LogHandler.Tag").GetValue().get(); + + // Get the severity level filter for the logger + auto fnTranslateSevFilter = [this](const std::string& rssLogFilter, sdv::core::ELogSeverity eDefaultSeverity) -> std::optional + { + if (rssLogFilter.empty()) return eDefaultSeverity; + else if (rssLogFilter == "Trace") return sdv::core::ELogSeverity::trace; + else if (rssLogFilter == "Debug") return sdv::core::ELogSeverity::debug; + else if (rssLogFilter == "Info") return sdv::core::ELogSeverity::info; + else if (rssLogFilter == "Warning") return sdv::core::ELogSeverity::warning; + else if (rssLogFilter == "Error") return sdv::core::ELogSeverity::error; + else if (rssLogFilter == "Fatal") return sdv::core::ELogSeverity::fatal; + + if (!IsConsoleSilent()) + std::cerr << "ERROR: Failed to process application log: invalid severity level filter: '" << rssLogFilter << "'" + << std::endl; + return {}; + }; + sdv::core::ELogSeverity eLogDefaultViewSeverityFilter = sdv::core::ELogSeverity::error; + switch (m_eAppContextType) + { + case sdv::app::EAppContext::main: + case sdv::app::EAppContext::isolated: + case sdv::app::EAppContext::external: + eLogDefaultViewSeverityFilter = sdv::core::ELogSeverity::info; + break; + default: + break; + } + auto optFilter = fnTranslateSevFilter(tableStartupConfig.GetDirect("LogHandler.Filter").GetValue(), + sdv::core::ELogSeverity::info); + if (!optFilter) return false; + m_eSeverityFilter = *optFilter; + optFilter = fnTranslateSevFilter(tableStartupConfig.GetDirect("LogHandler.ViewFilter").GetValue(), + eLogDefaultViewSeverityFilter); + if (!optFilter) return false; + m_eSeverityViewFilter = *optFilter; + + // Get the optional instance ID. + sdv::any_t anyInstanceID = tableStartupConfig.GetDirect("Application.Instance").GetValue(); + if (anyInstanceID) m_uiInstanceID = anyInstanceID; + else + m_uiInstanceID = 1000u; + + // Number of attempts to establish a connection to a running instance. + sdv::toml::CNode nodeRetries = tableStartupConfig.GetDirect("Connections.Retries"); + if (nodeRetries) + { + m_uiConnectRetries = nodeRetries.GetValue(); + if (m_uiConnectRetries > 30) + m_uiConnectRetries = 30; + else if (m_uiConnectRetries < 3) + m_uiConnectRetries = 3; + } + + // Main and isolated apps specific information. + if (IsMainApplication() || IsIsolatedApplication() || IsExternalApplication() || IsMaintenanceApplication()) + { + // Get the optional installation directory - either from the startup parameters (relative to exe), or the environment + // variable (relative to framework) or the exe when nothing else is specified. + m_pathRootDir = tableStartupConfig.GetDirect("Application.InstallDir").GetValueAsPath(); + if (!m_pathRootDir.empty() && m_pathRootDir.is_relative()) + m_pathRootDir = GetExecDirectory() / m_pathRootDir; + if (m_pathRootDir.empty()) + { +#ifdef _WIN32 + const wchar_t* szInstallDir = _wgetenv(L"SDV_COMPONENT_INSTALL"); + if (!szInstallDir) szInstallDir = _wgetenv(L"SDV_FRAMEWORK_RUNTIME"); + if (szInstallDir) m_pathRootDir = szInstallDir; +#elif defined __unix__ + const char* szInstallDir = getenv("SDV_COMPONENT_INSTALL"); + if (!szInstallDir) szInstallDir = getenv("SDV_FRAMEWORK_RUNTIME"); + if (szInstallDir) m_pathRootDir = std::filesystem::u8path(szInstallDir); +#else + #error OS not supported! +#endif + } + if (!m_pathRootDir.empty() && m_pathRootDir.is_relative()) + m_pathRootDir = GetExecDirectory() / m_pathRootDir; + if (m_pathRootDir.empty()) + m_pathRootDir = GetExecDirectory(); + + // Derive installation directory from the root directory + m_pathInstallDir = m_pathRootDir / std::to_string(m_uiInstanceID); + try + { + std::filesystem::create_directories(m_pathInstallDir); + } + catch (const std::filesystem::filesystem_error& rexcept) + { + if (!IsConsoleSilent()) + { + std::cerr << "Cannot create installation directory: " << m_pathInstallDir << std::endl; + std::cerr << " Reason: " << rexcept.what() << std::endl; + } + return false; + } + } + + // Set the default communication provider to shared memory. This might be overwritten by the settings. + m_ssDefaultComProvider = "DefaultSharedMemory"; + + // Read the settings... if existing. + // Maintenance, main and isolated applications cannot load specific configs. The others can specify a configuration file, but + // not auto-updateable. + switch (m_eAppContextType) + { + case sdv::app::EAppContext::main: + case sdv::app::EAppContext::isolated: + case sdv::app::EAppContext::external: + case sdv::app::EAppContext::maintenance: + if (!LoadSettings()) return false; + break; + default: + m_pathUserConfig = tableStartupConfig.GetDirect("Application.Config").GetValueAsPath(); + break; + } + + // The applications use the SDV_FRAMEWORK_RUNTIME, SDV_COMPONENT_INSTALL, SDV_FRAMEWORK_DEV_TOOLS and SDV_FRAMEWORK_DEV_INCLUDE + // environment variables to locate the core components. The core can, however, be executed in an executable from a different + // location. If the environment variable contains a relative path, the path to the core components is relative to the + // executable. Since the core can start other executables, the path would be relative to the other executables, which mights + // reside in a different location than the executable starting the core. Change the paths in the environment variables to an + // absolute path to prevent an invalid path to the core components. + auto fnMakeAbsolutePathEnvVariable = [](const std::string& rssVariable) + { + std::filesystem::path pathEnv; +#ifdef _WIN32 + const wchar_t* szEnvPath = _wgetenv(sdv::MakeWString(rssVariable).c_str()); + if (szEnvPath) + pathEnv = szEnvPath; +#elif defined __unix__ + const char* szEnvPath = getenv(rssVariable.c_str()); + if (szEnvPath) + pathEnv = szEnvPath; +#else + #error The OS is not supported! +#endif + if (!pathEnv.empty() && pathEnv.is_relative()) + { + pathEnv = (GetExecDirectory() / pathEnv).lexically_normal(); +#ifdef _WIN32 + // NOTE: In windows there are two environment variable stacks which need to be updated. + std::ignore = SetEnvironmentVariable(sdv::MakeWString(rssVariable).c_str(), pathEnv.native().c_str()); + std::ignore = _wputenv((sdv::MakeWString(rssVariable) + L"=" + pathEnv.native()).c_str()); +#elif defined __unix__ + std::ignore = setenv(rssVariable.c_str(), pathEnv.generic_u8string().c_str(), 1); +#else + #error The OS is not supported! +#endif + } + }; + fnMakeAbsolutePathEnvVariable("SDV_FRAMEWORK_RUNTIME"); + fnMakeAbsolutePathEnvVariable("SDV_COMPONENT_INSTALL"); + fnMakeAbsolutePathEnvVariable("SDV_FRAMEWORK_DEV_TOOLS"); + fnMakeAbsolutePathEnvVariable("SDV_FRAMEWORK_DEV_INCLUDE"); + + return true; +} + +bool CAppSettings::LoadSettings() +{ + // Check for the proper context + switch (m_eAppContextType) + { + case sdv::app::EAppContext::main: + case sdv::app::EAppContext::isolated: + case sdv::app::EAppContext::external: + case sdv::app::EAppContext::maintenance: + break; + default: + return true; // Not an error... + } + + // If the template is not existing, this is not an error... use the default template + std::string ssSettings; + if (!std::filesystem::exists(m_pathInstallDir / "settings.toml")) + ssSettings = g_ssSettingsTemplate; + else + { + std::ifstream fstream(m_pathInstallDir / "settings.toml"); + ssSettings = std::string((std::istreambuf_iterator(fstream)), std::istreambuf_iterator()); + fstream.close(); + } + + try + { + // Read the configuration + toml_parser::CParser parserSettings(ssSettings); + + // If there is no "Settings" table, this is not an e rror... + sdv::toml::CNodeCollection tableSettings(parserSettings.Root().GetNodeDirect("Settings")); + if (!tableSettings) return true; + + // Check for the version + uint32_t uiVersion = tableSettings.GetDirect("Version").GetValue(); + if (uiVersion != SDVFrameworkInterfaceVersion) + { + if (!IsConsoleSilent()) + std::cerr << "ERROR: Invalid version of application settings file (expected version " + << SDVFrameworkInterfaceVersion << ", but available version " << uiVersion << ")" << std::endl; + return false; + } + + // Get the platform config - but only when not specified over the app-control-config. + if (m_pathPlatformConfig.empty()) + m_pathPlatformConfig = tableSettings.GetDirect("PlatformConfig").GetValueAsPath(); + + // Get the vehicle interface config - but only when not specified over the app-control-config. + if (m_pathVehIfcConfig.empty()) + m_pathVehIfcConfig = tableSettings.GetDirect("VehIfcConfig").GetValueAsPath(); + + // Get the vehicle abstraction config - but only when not specified over the app-control-config. + if (m_pathVehAbstrConfig.empty()) + m_pathVehAbstrConfig = tableSettings.GetDirect("VehAbstrConfig").GetValueAsPath(); + + // Get the application config - but only when not specified over the app-control-config. + if (m_pathUserConfig.empty()) + m_pathUserConfig = tableSettings.GetDirect("AppConfig").GetValueAsPath(); + + // Reset the default communication provider. + m_ssDefaultComProvider.clear(); + + // Read the listener configurations + m_mapListeners.clear(); + sdv::toml::CNodeCollection arrayListeners = tableSettings.GetDirect("Listener"); + for (size_t nIndex = 0; nIndex < arrayListeners.GetCount(); nIndex++) + { + // Get the listener configuration + sdv::toml::CNodeCollection tableListener = arrayListeners.Get(nIndex); + if (!tableListener.IsValid()) + { + if (!IsConsoleSilent()) std::cerr << "ERROR: Invalid listener configuration element." << std::endl; + continue; + } + + // Get the configuration elements + std::string ssName = tableListener.GetDirect("Name").GetValueAsString(); + std::string ssProvider = tableListener.GetDirect("Provider").GetTOML(); + std::string ssProviderName = tableListener.GetDirect("Provider.Name").GetValueAsString(); + std::string ssChannelConfig = tableListener.GetDirect("IpcChannel").GetTOML(); + if (ssName.empty()) + { + if (!IsConsoleSilent()) std::cerr << "ERROR: Missing listener name." << std::endl; + continue; + } + if (ssProvider.empty() || ssProviderName.empty()) + { + if (!IsConsoleSilent()) + std::cerr << "ERROR: Missing listener provider name." << std::endl; + continue; + } + std::stringstream sstreamConfig; + sstreamConfig << "[Provider]" << std::endl << ssProvider << std::endl << "[IpcChannel]" << std::endl << + ssChannelConfig << std::endl; + if (!AddListenerConfig(ssName, sstreamConfig.str())) + { + if (!IsConsoleSilent()) + std::cerr << "ERROR: Could not add listener configuration (likely duplicate configuration)." << std::endl; + continue; + } + + // If running as main application, use the default config to extract the default communication provider. + if (m_eAppContextType == sdv::app::EAppContext::main && ssName == "Default") + m_ssDefaultComProvider = ssProviderName; + } + + // Is there a default listener. If not, add one based on the default shared memory component. + if (m_mapListeners.find("Default") == m_mapListeners.end()) + AddListenerConfig("Default", DefaultListenerConfig()); + + // Read the connection configurations + m_vecConnections.clear(); + sdv::toml::CNodeCollection arrayConnections = tableSettings.GetDirect("Connection"); + for (size_t nIndex = 0; nIndex < arrayConnections.GetCount(); nIndex++) + { + // Get the listener configuration + sdv::toml::CNodeCollection tableConnection = arrayConnections.Get(nIndex); + if (!tableConnection.IsValid()) + { + if (!IsConsoleSilent()) + std::cerr << "ERROR: Invalid connection configuration element." << std::endl; + continue; + } + + // Get the configuration elements + std::string ssName = tableConnection.GetDirect("Name").GetValueAsString(); + std::string ssProvider = tableConnection.GetDirect("Provider").GetTOML(); + std::string ssProviderName = tableConnection.GetDirect("Provider.Name").GetValueAsString(); + std::string ssChannelConfig = tableConnection.GetDirect("IpcChannel").GetTOML(); + if (ssName.empty()) + { + if (!IsConsoleSilent()) + std::cerr << "ERROR: Missing connection name." << std::endl; + continue; + } + if (ssProvider.empty() || ssProviderName.empty()) + { + if (!IsConsoleSilent()) + std::cerr << "ERROR: Missing connection provider name." << std::endl; + continue; + } + std::stringstream sstreamConfig; + sstreamConfig << "[Provider]" << std::endl << ssProvider << std::endl << "[IpcChannel]" << std::endl + << ssChannelConfig << std::endl; + if (!AddConnectionConfig(ssName, sstreamConfig.str())) + { + if (!IsConsoleSilent()) + std::cerr << "ERROR: Could not add connection configuration (likely duplicate configuration)." << std::endl; + continue; + } + + // If not running as main application, use the default config to extract the default communication provider. + if (m_eAppContextType != sdv::app::EAppContext::main && ssName == "Default") + m_ssDefaultComProvider = ssProviderName; + + } + + // Is there a default connection (only valid for external applications). If not, add one based on the default shared memory. + auto itConnection = std::find_if(m_vecConnections.begin(), + m_vecConnections.end(), + [](const auto& rprConnection) { return rprConnection.first == "Default"; }); + if (itConnection == m_vecConnections.end()) + AddConnectionConfig("Default", DefaultConnectionConfig(), + m_vecConnections.size() ? m_vecConnections.front().first : ""); + + // If there is no default communication provider, use the default shared memory provider. + if (m_ssDefaultComProvider.empty()) + m_ssDefaultComProvider = "DefaultSharedMemory"; + } + catch (const sdv::toml::XTOMLParseException& rexcept) + { + if (!IsConsoleSilent()) + std::cerr << "ERROR: Failed to parse application settings: " << rexcept.what() << std::endl; + return false; + } + return true; +} + +bool CAppSettings::SaveSettings() +{ + // Check for the proper context + switch (m_eAppContextType) + { + case sdv::app::EAppContext::maintenance: + break; + case sdv::app::EAppContext::main: + case sdv::app::EAppContext::isolated: + default: + return true; // Not an error... + } + + // Protect against multiple write actions at the same time. + ipc::named_mutex mtx("LockSdvAppSettings_" + std::to_string(m_uiInstanceID)); + // Warning of cppcheck for locking a local mutex, which doesn't have any effect. Since this is a named mutex between + // applciations, the warning is not correct. Suppress warning. + // cppcheck-suppress localMutex + std::unique_lock lock(mtx); + // If the template is not existing, create a default settings file... std::string ssSettings; bool bChangeDetected = false; if (!std::filesystem::exists(m_pathInstallDir / "settings.toml")) { bChangeDetected = true; - ssSettings = std::move(ssSettingsTemplate); - m_bPlatformConfig = true; - m_bVehIfcConfig = true; - m_bVehAbstrConfig = true; - m_bUserConfig = true; + ssSettings = std::move(g_ssSettingsTemplate); + m_bUpdatePlatformConfig = true; + m_bUpdateVehIfcConfig = true; + m_bUpdateVehAbstrConfig = true; + m_bUpdateUserConfig = true; } else { @@ -313,7 +584,7 @@ AppConfig = "" std::ifstream fstream(m_pathInstallDir / "settings.toml"); if (!fstream.is_open()) { - if (!m_bSilent) + if (!IsConsoleSilent()) std::cerr << "ERROR: Cannot open the application settings file." << std::endl; return false; } @@ -322,9 +593,9 @@ AppConfig = "" ssSettings = std::string((std::istreambuf_iterator(fstream)), std::istreambuf_iterator()); if (ssSettings.empty()) { - if (!m_bSilent) + if (!IsConsoleSilent()) std::cerr << "ERROR: Cannot read the application settings file; will use default." << std::endl; - ssSettings = std::move(ssSettingsTemplate); + ssSettings = std::move(g_ssSettingsTemplate); bChangeDetected = true; } } @@ -338,7 +609,7 @@ AppConfig = "" sdv::toml::CNodeCollection tableRoot(&parserSettings.Root()); if (!tableRoot) { - if (!m_bSilent) + if (!IsConsoleSilent()) std::cerr << "ERROR: Invalid TOML file '" << (m_pathInstallDir / "settings.toml").generic_u8string() << "'" << std::endl; @@ -349,7 +620,7 @@ AppConfig = "" tableSettings = tableRoot.AddTable("Settings"); if (!tableSettings) { - if (!m_bSilent) + if (!IsConsoleSilent()) std::cerr << "ERROR: Invalid 'Settings' table." << std::endl; return false; } @@ -357,7 +628,7 @@ AppConfig = "" uint32_t uiVersion = tableSettings.GetDirect("Version").GetValue(); if (uiVersion != SDVFrameworkInterfaceVersion) { - if (!m_bSilent) + if (!IsConsoleSilent()) std::cerr << "ERROR: Invalid version of application settings file (expected version " << SDVFrameworkInterfaceVersion << ", but available version " << uiVersion << ")" << std::endl; return false; @@ -366,26 +637,26 @@ AppConfig = "" // Generic update config file function auto fnUpdateConfig = [&](const std::string &rssConfigKey, const std::filesystem::path& rpathConfigFile) { - sdv::toml::CNode nodeUserConfig = tableSettings.GetDirect(rssConfigKey); - if (nodeUserConfig.GetValue().empty()) + sdv::toml::CNode nodeConfig = tableSettings.GetDirect(rssConfigKey); + if (nodeConfig.GetValue().empty()) { bChangeDetected = true; - if (nodeUserConfig) - nodeUserConfig.Delete(); - nodeUserConfig = tableSettings.AddValue(rssConfigKey, rpathConfigFile); - if (!nodeUserConfig) + if (nodeConfig) + nodeConfig.Delete(); + nodeConfig = tableSettings.AddValue(rssConfigKey, rpathConfigFile); + if (!nodeConfig) { - if (!m_bSilent) std::cerr << "ERROR: Cannot insert the \"Settings." << rssConfigKey << + if (!IsConsoleSilent()) std::cerr << "ERROR: Cannot insert the \"Settings." << rssConfigKey << "\" value; cannot process further." << std::endl; return false; } } - else if (nodeUserConfig.GetValue() != rpathConfigFile) + else if (nodeConfig.GetValue() != rpathConfigFile) { bChangeDetected = true; - if (!nodeUserConfig.SetValue(rpathConfigFile)) + if (!nodeConfig.SetValue(rpathConfigFile)) { - if (!m_bSilent) std::cerr << "ERROR: Cannot update the \"Settings." << rssConfigKey << + if (!IsConsoleSilent()) std::cerr << "ERROR: Cannot update the \"Settings." << rssConfigKey << "\" value; cannot process further." << std::endl; return false; } @@ -394,10 +665,203 @@ AppConfig = "" }; // Update the configuration path values. - if (m_bPlatformConfig && !fnUpdateConfig("PlatformConfig", m_pathPlatformConfig)) return false; - if (m_bVehIfcConfig && !fnUpdateConfig("VehIfcConfig", m_pathVehIfcConfig)) return false; - if (m_bVehAbstrConfig && !fnUpdateConfig("VehAbstrConfig", m_pathVehAbstrConfig)) return false; - if (m_bUserConfig && !fnUpdateConfig("AppConfig", m_pathUserConfig)) return false; + if (m_bUpdatePlatformConfig && !fnUpdateConfig("PlatformConfig", m_pathPlatformConfig)) return false; + if (m_bUpdateVehIfcConfig && !fnUpdateConfig("VehIfcConfig", m_pathVehIfcConfig)) return false; + if (m_bUpdateVehAbstrConfig && !fnUpdateConfig("VehAbstrConfig", m_pathVehAbstrConfig)) return false; + if (m_bUpdateUserConfig && !fnUpdateConfig("AppConfig", m_pathUserConfig)) return false; + + // Update the listener configuration - do not add "Default" config + sdv::toml::CNode nodeListeners = tableSettings.GetDirect("Listener"); + if (nodeListeners && nodeListeners.GetType() != sdv::toml::ENodeType::node_array) + { + nodeListeners.Delete(); + bChangeDetected = true; + } + + // Check whether each listener in the array is also present in the map. + // - if not or if different, remove it from the array + // - remove it from the map if identical + sdv::toml::CNodeCollection arrayListeners(nodeListeners); + auto mapListenersCopy = m_mapListeners; + size_t nListenerCount = arrayListeners.GetCount(); // Do not request anything from arrayListeners during the for + // loop. When deleting the last entry, the array is deleted as well, + // rendering the the interface pointer to void. + for (size_t nIndex = 0; nIndex < nListenerCount; ++nIndex) + { + // Find the listener + sdv::toml::CNode nodeListener = arrayListeners.Get(nIndex); + + // Check for a correct listener + if (nodeListener.GetType() != sdv::toml::ENodeType::node_table) + { + nodeListener.Delete(); + nIndex--; + nListenerCount--; + bChangeDetected = true; + continue; + } + + // Check for an identical listener + sdv::toml::CNodeCollection tableListener = nodeListener; + if (!tableListener) break; // Should not occur + + // Get the configuration elements + std::string ssStoredName = tableListener.GetDirect("Name").GetValueAsString(); + std::string ssStoredProvider = tableListener.GetDirect("Provider").GetTOML(); + std::string ssStoredChannelConfig = tableListener.GetDirect("IpcChannel").GetTOML(); + std::stringstream sstreamStoredConfig; + sstreamStoredConfig << "[Provider]" << std::endl << ssStoredProvider << std::endl << "[IpcChannel]" << std::endl + << ssStoredChannelConfig << std::endl; + auto itListener = mapListenersCopy.find(ssStoredName); + std::string ssConfig; + if (itListener != mapListenersCopy.end()) + ssConfig = itListener->second; + + // Check for identical configuration + if (!toml_parser::CompareEqual(ssConfig, sstreamStoredConfig.str())) + { + // Config is different the entry and try again. + nodeListener.Delete(); + nIndex--; + nListenerCount--; + bChangeDetected = true; + continue; + } + + // The node and the map entry are identical. Remove this entry from the map. + mapListenersCopy.erase(itListener); + } + + // Add each leftover listener from the map to the array. + for (const auto& rvtListener : mapListenersCopy) + { + // Skip the "Default" configuration + if (rvtListener.first == "Default" && rvtListener.second == DefaultListenerConfig()) continue; + sdv::toml::CNodeCollection tableListener = tableSettings.AddTableArray("Listener"); + + // Add config + tableListener.AddValue("Name", rvtListener.first); + tableListener.AddTOML(rvtListener.second); + bChangeDetected = true; + } + + // Update the connection configuration - do not add "Default" config + sdv::toml::CNode nodeConnections = tableSettings.GetDirect("Connection"); + if (nodeConnections && nodeConnections.GetType() != sdv::toml::ENodeType::node_array) + { + nodeConnections.Delete(); + bChangeDetected = true; + } + + // Check whether each connection in the array is also present in the vector + // - if not or if different, remove it from the array + sdv::toml::CNodeCollection arrayConnections(nodeConnections); + size_t nConnectionCount = arrayConnections.GetCount(); // Do not request anything from arrayConnections during the + // for-loop. When deleting the last entry, the array is deleted as + // well, rendering the the interface pointer to void. + for (size_t nIndex = 0; nIndex < nConnectionCount; ++nIndex) + { + // Find the connection + sdv::toml::CNode nodeConnection = arrayConnections.Get(nIndex); + + // Check for a correct connection + if (nodeConnection.GetType() != sdv::toml::ENodeType::node_table) + { + nodeConnection.Delete(); + nIndex--; + nConnectionCount--; + bChangeDetected = true; + continue; + } + + // Check for an identical connection + sdv::toml::CNodeCollection tableConnection = nodeConnection; + if (!tableConnection) break; // Should not occur + + // Get the configuration elements + std::string ssStoredName = tableConnection.GetDirect("Name").GetValueAsString(); + std::string ssStoredProvider = tableConnection.GetDirect("Provider").GetTOML(); + std::string ssStoredChannelConfig = tableConnection.GetDirect("IpcChannel").GetTOML(); + std::stringstream sstreamStoredConfig; + sstreamStoredConfig << "[Provider]" << std::endl << ssStoredProvider << std::endl << "[IpcChannel]" << std::endl << + ssStoredChannelConfig << std::endl; + auto itConnection = std::find_if(m_vecConnections.begin(), m_vecConnections.end(), [&](const auto& rprConnection) + { return rprConnection.first == ssStoredName; }); + std::string ssConfig; + if (itConnection != m_vecConnections.end()) + ssConfig = itConnection->second; + + // Check for identical configuration + if (!toml_parser::CompareEqual(ssConfig, sstreamStoredConfig.str())) + { + // Config is different. Remove the entry and try again. + nodeConnection.Delete(); + nIndex--; + nConnectionCount--; + bChangeDetected = true; + continue; + } + } + + // Request the array once more. Could be deleted in the mean time. + nodeConnections = tableSettings.GetDirect("Connection"); + arrayConnections = sdv::toml::CNodeCollection(nodeConnections); + + // Check for each connection in the vector if present at the correct position in the map + // - if not correct position, take the TOML and remove and reinsert at the correct position + // - if not available, add to the array + //size_t nParentIndex = 0; + size_t nOffset = 0; + for (size_t nIndex = 0; nIndex < m_vecConnections.size(); ++nIndex) + { + // Skip the "Default" configuration + if (m_vecConnections[nIndex].first == "Default" && m_vecConnections[nIndex].second == DefaultConnectionConfig()) + { + nOffset++; + continue; + } + + + // Get the connection name + std::string ssName = m_vecConnections[nIndex].first; + + // Check index + std::string ssToml; + bool bFoundAndCorrect = false; + for (size_t nIndex2 = nIndex - nOffset; nIndex2 < arrayConnections.GetCount(); ++nIndex2) + { + sdv::toml::CNodeCollection tableConnection(arrayConnections.Get(nIndex2)); + std::string ssStoredName = tableConnection.GetDirect("Name").GetValueAsString(); + if (ssStoredName == ssName) + { + if (nIndex2 != nIndex - nOffset) + ssToml = tableConnection.GetTOML(); + else + bFoundAndCorrect = true; + break; + } + } + + // Found and correct? Process next + if (bFoundAndCorrect) continue; + + // Is there a TOML? Then add a connection with the TOML + // Otherwise create a new connection + if (!ssToml.empty()) + arrayConnections.InsertTOML("", ssToml); + else + { + sdv::toml::CNodeCollection tableConnection; + if(arrayConnections) + tableConnection = arrayConnections.InsertTable(arrayConnections.GetNodeNameByIndex(0), "Connection"); + else + tableConnection = tableSettings.InsertTableArray("", "Connection"); + tableConnection.AddValue("Name", ssName); + tableConnection.AddTOML(m_vecConnections[nIndex].second); + } + + bChangeDetected = true; + } // Save the settings file if needed if (bChangeDetected) @@ -405,21 +869,21 @@ AppConfig = "" std::ofstream fstream(m_pathInstallDir / "settings.toml", std::ios::trunc); if (!fstream.is_open()) { - if (!m_bSilent) + if (!IsConsoleSilent()) std::cerr << "ERROR: Cannot write the application settings file." << std::endl; return false; } fstream << parserSettings.GenerateTOML(); - m_bPlatformConfig = false; - m_bVehIfcConfig = false; - m_bVehAbstrConfig = false; - m_bUserConfig = false; + m_bUpdatePlatformConfig = false; + m_bUpdateVehIfcConfig = false; + m_bUpdateVehAbstrConfig = false; + m_bUpdateUserConfig = false; } } catch (const sdv::toml::XTOMLParseException& rexcept) { - if (!m_bSilent) + if (!IsConsoleSilent()) std::cerr << "ERROR: Failed to parse application settings: " << rexcept.what() << std::endl; return false; } @@ -428,37 +892,47 @@ AppConfig = "" bool CAppSettings::IsMainApplication() const { - return m_eContextMode == sdv::app::EAppContext::main; + return m_eAppContextType == sdv::app::EAppContext::main; } bool CAppSettings::IsIsolatedApplication() const { - return m_eContextMode == sdv::app::EAppContext::isolated; + return m_eAppContextType == sdv::app::EAppContext::isolated; } bool CAppSettings::IsStandaloneApplication() const { - return m_eContextMode == sdv::app::EAppContext::standalone; + return m_eAppContextType == sdv::app::EAppContext::standalone; } bool CAppSettings::IsEssentialApplication() const { - return m_eContextMode == sdv::app::EAppContext::essential; + return m_eAppContextType == sdv::app::EAppContext::essential; } bool CAppSettings::IsMaintenanceApplication() const { - return m_eContextMode == sdv::app::EAppContext::maintenance; + return m_eAppContextType == sdv::app::EAppContext::maintenance; } bool CAppSettings::IsExternalApplication() const { - return m_eContextMode == sdv::app::EAppContext::external; + return m_eAppContextType == sdv::app::EAppContext::external; +} + +void CAppSettings::SetContextType(sdv::app::EAppContext eContextType) +{ + m_eAppContextType = eContextType; } sdv::app::EAppContext CAppSettings::GetContextType() const { - return m_eContextMode; + return m_eAppContextType; +} + +void CAppSettings::SetInstanceID(uint32_t uiID) +{ + m_uiInstanceID = uiID; } uint32_t CAppSettings::GetInstanceID() const @@ -466,11 +940,6 @@ uint32_t CAppSettings::GetInstanceID() const return m_uiInstanceID; } -uint32_t CAppSettings::GetRetries() const -{ - return m_uiRetries; -} - std::string CAppSettings::GetLoggerClass() const { return m_ssLoggerClass; @@ -498,12 +967,32 @@ sdv::core::ELogSeverity CAppSettings::GetConsoleSeverityFilter() const bool CAppSettings::IsConsoleSilent() const { - return m_bSilent; + return m_eConsoleReporting == EAppConsoleReporting::silent; } bool CAppSettings::IsConsoleVerbose() const { - return m_bVerbose; + return m_eConsoleReporting == EAppConsoleReporting::verbose; +} + +void CAppSettings::SetConsoleReporting(EAppConsoleReporting eReporting) +{ + m_eConsoleReporting = eReporting; +} + +CAppSettings::EAppConsoleReporting CAppSettings::GetConsoleReporting() const +{ + return m_eConsoleReporting; +} + +bool CAppSettings::RedirectMonitorToConsole() const +{ + return m_bRedirectMon; +} + +std::filesystem::path CAppSettings::GetFrameworkDir() const +{ + return m_pathFrameworkDir; } std::filesystem::path CAppSettings::GetRootDir() const @@ -530,29 +1019,28 @@ std::vector CAppSettings::GetSystemConfigPaths() const std::filesystem::path CAppSettings::GetConfigPath(EConfigType eType) const { - // Is running as main application? + // Is running as main or maintenance application? if (!IsMainApplication() && !IsMaintenanceApplication()) return {}; switch (eType) { case EConfigType::platform_config: if (!m_pathPlatformConfig.empty()) return m_pathPlatformConfig; - return "platform.toml"; + return m_bUpdatePlatformConfig ? "" : "platform.toml"; case EConfigType::vehicle_interface_config: if (!m_pathVehIfcConfig.empty()) return m_pathVehIfcConfig; - return "vehicle_ifc.toml"; + return m_bUpdateVehIfcConfig ? "" : "vehicle_ifc.toml"; case EConfigType::vehicle_abstraction_config: if (!m_pathVehAbstrConfig.empty()) return m_pathVehAbstrConfig; - return "vehicle_abstract.toml"; + return m_bUpdateVehAbstrConfig ? "" : "vehicle_abstract.toml"; case EConfigType::user_config: if (!m_pathUserConfig.empty()) return m_pathUserConfig; - return "app_config.toml"; + return m_bUpdateUserConfig ? "" : "app_config.toml"; default: return {}; } } - bool CAppSettings::EnableConfig(EConfigType eType) { // Is running as main application? @@ -561,22 +1049,22 @@ bool CAppSettings::EnableConfig(EConfigType eType) switch (eType) { case EConfigType::platform_config: - m_bPlatformConfig = true; + m_bUpdatePlatformConfig = true; if (!m_pathPlatformConfig.empty()) return true; m_pathPlatformConfig = "platform.toml"; break; case EConfigType::vehicle_interface_config: - m_bVehIfcConfig = true; + m_bUpdateVehIfcConfig = true; if (!m_pathVehIfcConfig.empty()) return true; m_pathVehIfcConfig = "vehicle_ifc.toml"; break; case EConfigType::vehicle_abstraction_config: - m_bVehAbstrConfig = true; + m_bUpdateVehAbstrConfig = true; if (!m_pathVehAbstrConfig.empty()) return true; m_pathVehAbstrConfig = "vehicle_abstract.toml"; break; case EConfigType::user_config: - m_bUserConfig = true; + m_bUpdateUserConfig = true; if (!m_pathUserConfig.empty()) return true; m_pathUserConfig = "app_config.toml"; break; @@ -596,19 +1084,19 @@ bool CAppSettings::DisableConfig(EConfigType eType) { case EConfigType::platform_config: m_pathPlatformConfig.clear(); - m_bPlatformConfig = true; + m_bUpdatePlatformConfig = true; break; case EConfigType::vehicle_interface_config: m_pathVehIfcConfig.clear(); - m_bVehIfcConfig = true; + m_bUpdateVehIfcConfig = true; break; case EConfigType::vehicle_abstraction_config: m_pathVehAbstrConfig.clear(); - m_bVehAbstrConfig = true; + m_bUpdateVehAbstrConfig = true; break; case EConfigType::user_config: m_pathUserConfig.clear(); - m_bUserConfig = true; + m_bUpdateUserConfig = true; break; default: return false; @@ -625,11 +1113,18 @@ std::filesystem::path CAppSettings::GetUserConfigPath() const bool CAppSettings::SetUserConfigPath(const std::filesystem::path& rpathConfig) { // Is running as server application? Then the user configuration is limited to a filename only. - if (IsMainApplication() || IsMaintenanceApplication() || IsIsolatedApplication()) + switch (m_eAppContextType) { + case sdv::app::EAppContext::main: + case sdv::app::EAppContext::isolated: + case sdv::app::EAppContext::external: + case sdv::app::EAppContext::maintenance: // Must be a filename only. if (!rpathConfig.has_filename() || rpathConfig.has_parent_path()) return false; + break; + default: + break; } // Assign the path @@ -649,63 +1144,165 @@ bool CAppSettings::RemoveUserConfigPath() return true; } -sdv::sequence CAppSettings::GetNames() const +const std::string& CAppSettings::GetDefaultComProvider() const { - sdv::sequence seqNames = {"app.instance_id", "console.info_level"}; - return seqNames; + return m_ssDefaultComProvider; } -sdv::any_t CAppSettings::Get(/*in*/ const sdv::u8string& ssAttribute) const +sdv::sequence CAppSettings::GetListeners() const { - if (ssAttribute == "app.instance_id") - return sdv::any_t(m_uiInstanceID); - if (ssAttribute == "console.info_level") - { - if (m_bSilent) - return "silent"; - if (m_bVerbose) - return "verbose"; - return "normal"; - } - return {}; + // Is running as main or maintenance application? + if (!IsMainApplication() && !IsMaintenanceApplication()) return {}; + + // Add the listener names + sdv::sequence seqListeners; + for (const auto& rvtListener : m_mapListeners) + seqListeners.push_back(rvtListener.first); + + return seqListeners; } -bool CAppSettings::Set(/*in*/ const sdv::u8string& /*ssAttribute*/, /*in*/ sdv::any_t /*anyAttribute*/) +sdv::u8string CAppSettings::GetListenerConfig(/*in*/ const sdv::u8string& ssName) const { - // Currently there are not setting attributes... - return false; + // Is running as main or maintenance application? + if (!IsMainApplication() && !IsMaintenanceApplication()) return {}; + + // Find the listener + auto itListener = m_mapListeners.find(ssName); + if (itListener == m_mapListeners.end()) return {}; + return itListener->second; } -uint32_t CAppSettings::GetFlags(/*in*/ const sdv::u8string& ssAttribute) const +bool CAppSettings::AddListenerConfig(/*in*/ const sdv::u8string& ssName, /*in*/ const sdv::u8string& ssConfig) { - if (ssAttribute == "app.instance_id") - return hlpr::flags(sdv::EAttributeFlags::read_only); - if (ssAttribute == "console.info_level") - return hlpr::flags(sdv::EAttributeFlags::read_only); - return 0u; + // Is running as main or maintenance application? + if (!IsMainApplication() && !IsMaintenanceApplication()) return {}; + + // Add the listener + m_mapListeners[ssName] = ssConfig; + + return true; +} + +bool CAppSettings::RemoveListenerConfig(/*in*/ const sdv::u8string& ssName) +{ + // Is running as main or maintenance application? + if (!IsMainApplication() && !IsMaintenanceApplication()) return false; + + // Erase a listener if existing + return m_mapListeners.erase(ssName) ? true : false; +} + +sdv::sequence CAppSettings::GetConnections() const +{ + // Is running as main, external or maintenance application? + if (!IsMainApplication() && !IsExternalApplication() && !IsMaintenanceApplication()) return {}; + + // Add the connection names + sdv::sequence seqConnections; + for (const auto& rprConnection : m_vecConnections) + seqConnections.push_back(rprConnection.first); + + return seqConnections; +} + +sdv::u8string CAppSettings::GetConnectionConfig(/*in*/ const sdv::u8string& ssName) const +{ + // Is running as main, external or maintenance application? + if (!IsMainApplication() && !IsExternalApplication() && !IsMaintenanceApplication()) return {}; + + // Find the connection + auto itConnection = std::find_if(m_vecConnections.begin(), m_vecConnections.end(), [&](const auto& rprConnection) + { return rprConnection.first == ssName; }); + if (itConnection == m_vecConnections.end()) return {}; + return itConnection->second; +} + +bool CAppSettings::AddConnectionConfig(/*in*/ const sdv::u8string& ssName, /*in*/ const sdv::u8string& ssConfig, + /*in*/ const sdv::u8string& ssInsertBefore /*= std::string()*/) +{ + // Is running as main, external or maintenance application? + if (!IsMainApplication() && !IsExternalApplication() && !IsMaintenanceApplication()) return {}; + + // Check for an existing connection; erase if existing. + auto itConnection = std::find_if(m_vecConnections.begin(), m_vecConnections.end(), [&](const auto& rprConnection) + { return rprConnection.first == ssName; }); + if (itConnection != m_vecConnections.end()) m_vecConnections.erase(itConnection); + + // Find the connection to insert before + auto itInsertBefore = std::find_if(m_vecConnections.begin(), m_vecConnections.end(), [&](const auto& rprConnection) + { return rprConnection.first == ssInsertBefore; }); + + // Add the connection + m_vecConnections.insert(itInsertBefore, std::make_pair(ssName, ssConfig)); + + return true; +} + +bool CAppSettings::RemoveConnectionConfig(/*in*/ const sdv::u8string& ssName) +{ + // Is running as main, external or maintenance application? + if (!IsMainApplication() && !IsExternalApplication() && !IsMaintenanceApplication()) return false; + + // Check for an existing connection + auto itConnection = std::find_if(m_vecConnections.begin(), m_vecConnections.end(), [&](const auto& rprConnection) + { return rprConnection.first == ssName; }); + if (itConnection != m_vecConnections.end()) + m_vecConnections.erase(itConnection); + else + return false; + return true; +} + +uint32_t CAppSettings::GetConnectRetries() const +{ + return m_uiConnectRetries; } void CAppSettings::Reset() { - m_eContextMode = sdv::app::EAppContext::no_context; + m_eAppContextType = sdv::app::EAppContext::no_context; m_pathLoggerModule.clear(); m_ssLoggerClass.clear(); m_ssProgramTag.clear(); m_eSeverityFilter = sdv::core::ELogSeverity::info; m_eSeverityViewFilter = sdv::core::ELogSeverity::error; m_uiInstanceID = 0u; - m_bSilent = false; - m_bVerbose = false; + m_uiConnectRetries = 5u; + m_eConsoleReporting = EAppConsoleReporting::normal; + m_bRedirectMon = false; m_pathRootDir.clear(); m_pathInstallDir.clear(); m_pathPlatformConfig.clear(); m_pathVehIfcConfig.clear(); m_pathVehAbstrConfig.clear(); m_pathUserConfig.clear(); - m_bPlatformConfig = false; - m_bVehIfcConfig = false; - m_bVehAbstrConfig = false; - m_bUserConfig = false; + m_bUpdatePlatformConfig = false; + m_bUpdateVehIfcConfig = false; + m_bUpdateVehAbstrConfig = false; + m_bUpdateUserConfig = false; + m_mapListeners.clear(); + m_vecConnections.clear(); + m_ssDefaultComProvider.clear(); +} + +std::string CAppSettings::DefaultListenerConfig() const +{ + return R"toml([Provider] +Name = "DefaultSharedMemory" +[IpcChannel] +Name = "DEFAULT_CHANNEL_)toml" + std::to_string(m_uiInstanceID) + R"toml(" +Size = 1024 +)toml"; +} + +std::string CAppSettings::DefaultConnectionConfig() const +{ + return R"toml([Provider] +Name = "DefaultSharedMemory" +[IpcChannel] +Name = "DEFAULT_CHANNEL_)toml" + std::to_string(m_uiInstanceID) + R"toml(" +)toml"; } CAppSettings& CAppSettingsService::GetAppSettings() diff --git a/sdv_services/core/app_settings.h b/sdv_services/core/app_settings.h index 1e53a87..716e719 100644 --- a/sdv_services/core/app_settings.h +++ b/sdv_services/core/app_settings.h @@ -62,6 +62,10 @@ * #Console output * [Console] * Report = "Silent" # Either "Silent", "Normal" or "Verbose" for no, normal or extensive messages. + * RedirectMon = true # When set, redirects messages to the console instead of the monitor application in main mode. + * + * [Connections] + * Retries = 5 # The amount of retries when trying to connect (value between 3 and 30, default is 5). * * # Search directories * @endcode @@ -103,11 +107,40 @@ * # Example * # AppConfig = "app_config.toml" * AppConfig = "" + * + * # A list of zero or more listener definitions that should be instantiated during startup of the + * # main application. If no listener definition is available, the default shared-memory listener + * # is being instantiated. + * # + * # [[Settings.Listener]] + * # Name = "" + * # [Settings.Listerner.Provider] + * # Name = "" + * # [Settings.Listener.IpcChannel] + * + * # A list of zero or more client connections that should be instantiated during startup. + * # + * # [[Settings.Connection]] + * # Name = "" + * # [Settings.Connection.Provider] + * # Name = "" + * # [Settings.Listener.IpcChannel] * @endcode */ -class CAppSettings : public sdv::IInterfaceAccess, public sdv::app::IAppContext, public sdv::IAttributes +class CAppSettings : public sdv::CSdvParamMap, public sdv::IInterfaceAccess, public sdv::app::IAppContext, + public sdv::app::IAppSettingsPersist, public sdv::app::IAppConnections { public: + /** + * @brief Console reporting. + */ + enum class EAppConsoleReporting + { + silent, ///< No reporting by application control (default) + normal, ///< Normal reporting by application control + verbose, ///< Extensive reporting by application control + }; + /** * @brief Constructor */ @@ -118,11 +151,71 @@ public: */ ~CAppSettings(); +#ifndef DOXYGEN_IGNORE // Interface map BEGIN_SDV_INTERFACE_MAP() SDV_INTERFACE_ENTRY(sdv::app::IAppContext) END_SDV_INTERFACE_MAP() + // Application mode labels + BEGIN_SDV_LABEL_MAP(sdv::app::EAppContext) + SDV_LABEL_ENTRY(sdv::app::EAppContext::no_context, "Undefined") + SDV_LABEL_ENTRY(sdv::app::EAppContext::standalone, "Standalone") + SDV_LABEL_ENTRY(sdv::app::EAppContext::external, "External") + SDV_LABEL_ENTRY(sdv::app::EAppContext::isolated, "Isolated") + SDV_LABEL_ENTRY(sdv::app::EAppContext::main, "Main") + SDV_LABEL_ENTRY(sdv::app::EAppContext::essential, "Essential") + SDV_LABEL_ENTRY(sdv::app::EAppContext::maintenance, "Maintenance") + END_SDV_LABEL_MAP() + + // Log severity labels + BEGIN_SDV_LABEL_MAP(sdv::core::ELogSeverity) + SDV_LABEL_ENTRY(sdv::core::ELogSeverity::trace, "Trace") + SDV_LABEL_ENTRY(sdv::core::ELogSeverity::debug, "Debug") + SDV_LABEL_ENTRY(sdv::core::ELogSeverity::info, "Info") + SDV_LABEL_ENTRY(sdv::core::ELogSeverity::warning, "Warning") + SDV_LABEL_ENTRY(sdv::core::ELogSeverity::error, "Error") + SDV_LABEL_ENTRY(sdv::core::ELogSeverity::fatal, "Fatal") + END_SDV_LABEL_MAP() + + // Console reporting labels + BEGIN_SDV_LABEL_MAP(EAppConsoleReporting) + SDV_LABEL_ENTRY(EAppConsoleReporting::normal, "Normal") + SDV_LABEL_ENTRY(EAppConsoleReporting::silent, "Silent") + SDV_LABEL_ENTRY(EAppConsoleReporting::verbose, "Verbose") + END_SDV_LABEL_MAP() + + // Parameter map + BEGIN_SDV_PARAM_MAP() + SDV_PARAM_SET_READONLY() + SDV_PARAM_GROUP("LogHandler") + SDV_PARAM_ENTRY(m_ssLoggerClass, "Class", "", "", "Component class name of a custom logger") + SDV_PARAM_ENTRY(m_pathLoggerModule, "Path", "", "", "Component module path of a custom logger") + SDV_PARAM_ENTRY(m_ssProgramTag, "Tag", "", "", "Program tag to use instead of the name SDV_LOG_") + SDV_PARAM_ENUM_ENTRY(m_eSeverityFilter, "Filter", sdv::core::ELogSeverity::info, + "Lowest severity filter to use when logging (Trace, Debug, Info, Warning, Error, Fatal)") + SDV_PARAM_ENUM_ENTRY(m_eSeverityViewFilter, "ViewFilter", sdv::core::ELogSeverity::error, + "Lowest severity filter to use when logging (Trace, Debug, Info, Warning, Error, Fatal)") + + SDV_PARAM_GROUP("Application") + SDV_PARAM_ENUM_ENTRY(m_eAppContextType, "Mode", sdv::app::EAppContext::no_context, "Application mode") + SDV_PARAM_ENTRY(m_uiInstanceID, "Instance", 0u, "", "System instance ID") + SDV_PARAM_ENTRY(m_pathRootDir, "RootDir", "", "", "Location of user component root directory") + SDV_PARAM_ENTRY(m_pathInstallDir, "InstallDir", "", "", "Location of user component installations") + SDV_PARAM_ENTRY(m_pathPlatformConfig, "PlatformConfig", "", "", "The platform abstraction configuration file") + SDV_PARAM_ENTRY(m_pathVehIfcConfig, "VehIfcConfig", "", "", "The vehicle interface configuration file") + SDV_PARAM_ENTRY(m_pathVehAbstrConfig, "VehAbstrConfig", "", "", "The vehicle abstraction configuration file") + SDV_PARAM_ENTRY(m_pathUserConfig, "AppConfig", "", "", "The application configuration file") + + SDV_PARAM_GROUP("Console") + SDV_PARAM_ENUM_ENTRY(m_eConsoleReporting, "Reporting", EAppConsoleReporting::normal, "Console reporting (Normal, Silent, Verbose)") + SDV_PARAM_ENTRY(m_bRedirectMon, "RedirectMon", false, "", "Redirect messages from the monitor onto the console.") + + SDV_PARAM_GROUP("Communication") + SDV_PARAM_ENTRY(m_ssDefaultComProvider, "DefaultProvider", "", "", "Name of the default communication provider") + END_SDV_PARAM_MAP() +#endif // !defined DOXYGEN_IGNORE + /** * @brief Process the application starrtup configuration. * @param[in] rssConfig Reference to the configuration content (TOML format). @@ -131,19 +224,20 @@ public: bool ProcessAppStartupConfig(const sdv::u8string& rssConfig); /** - * @brief Load the application settings file. + * @brief Load the application settings file. Overload of sdv::app::IAppSettingsPersist::LoadSettings. * @attention Only works if the application is running in main, isolation or maintenance mode. * @remarks When there is no settings file, this is not an error. Default settings will be assumed. * @return Returns whether the loading was successful. */ - bool LoadSettingsFile(); + bool LoadSettings() override; /** * @brief Save the application settings file (or create when not existing yet). - * @attention Only works if the application is running in main, isolation or maintenance mode. + * Overload of sdv::app::IAppSettingsPersist::SaveSettings. + * @attention Only works if the application is running in maintenance mode. * @return Returns whether the saving was successful. */ - bool SaveSettingsFile(); + bool SaveSettings() override; /** * @brief Return whether the current application is the main application. @@ -181,27 +275,33 @@ public: */ bool IsExternalApplication() const; + /** + * @brief Set the application context type. + * @param[in] eContextType The application context type to set. + */ + void SetContextType(sdv::app::EAppContext eContextType); + /** * @brief Return the application context mode. Overload of sdv::app::IAppContext::GetContextType. * @return The context mode. */ sdv::app::EAppContext GetContextType() const override; + /** + * @brief Set the instance ID. + * @param uiID The instance ID to set. + */ + void SetInstanceID(uint32_t uiID); + /** * @brief Return the core instance ID. Overload of sdv::app::IAppContext::GetContextType. * @details Get the instance. If not otherwise specified, the current instance depends on whether the application is running - * as main or isolated application, in which case the instance is 1000. In all other cases the instance is 0. An instance - * ID can be supplied through the app startup configuration. + * as main, isolated or maintenance application, in which case the instance is 1000. In all other cases the instance is 0. An + * instance ID can be supplied through the app startup configuration. * @return The core instance ID. */ uint32_t GetInstanceID() const override; - /** - * @brief Return the number of retries to establish a connection. Overload of sdv::app::IAppContext::GetRetries. - * @return Number of retries. - */ - uint32_t GetRetries() const override; - /** * @brief Get the class name of a logger service, if specified in the application startup configuration. * @return The logger class name. @@ -244,6 +344,30 @@ public: */ bool IsConsoleVerbose() const; + /** + * @brief Set the application console reporting status. + * @param eReporting The console reporting status to set. + */ + void SetConsoleReporting(EAppConsoleReporting eReporting); + + /** + * @brief Get the current application console reporting status. + * @return The console reporting status of the application. + */ + EAppConsoleReporting GetConsoleReporting() const; + + /** + * @brief Redirect the messages monitored for main application onto the console. + * @return Returns whether redirection is switched on. + */ + bool RedirectMonitorToConsole() const; + + /** + * @brief Get the framework directory for the application. + * @return The location of framework directory. + */ + std::filesystem::path GetFrameworkDir() const; + /** * @brief Get the root directory for the application. * @remarks Is only valid when used in main, isolated and maintenance applications. @@ -279,6 +403,7 @@ public: /** * @brief Get the stored or default configuration path name. * @attention Setting a path is only valid when running as main application. + * @param[in] eType The configuration type to get the path for. * @return The path name dependent on the configuration type. If no path name was configured, the default path name is returned. */ std::filesystem::path GetConfigPath(EConfigType eType) const; @@ -326,33 +451,80 @@ public: bool RemoveUserConfigPath(); /** - * @brief Get a sequence with the available attribute names. Overload of sdv::IAttributes::GetNames. - * @return The sequence of attribute names. + * @brief Get the default communication provider (extracted from listener and connection settings). + * @return Name of the default communication provider. */ - virtual sdv::sequence GetNames() const override; + const std::string& GetDefaultComProvider() const; /** - * @brief Get the attribute value. Overload of sdv::IAttributes::Get. - * @param[in] ssAttribute Name of the attribute. - * @return The attribute value or an empty any-value if the attribute wasn't found or didn't have a value. + * @brief Get a sequence with listener names. Overload of sdv::app::IAppConnections::GetListeners. + * @return Sequence with listener name strings. */ - virtual sdv::any_t Get(/*in*/ const sdv::u8string& ssAttribute) const override; + sdv::sequence GetListeners() const override; /** - * @brief Set the attribute value. Overload of sdv::IAttributes::Set. - * @param[in] ssAttribute Name of the attribute. - * @param[in] anyAttribute Attribute value to set. - * @return Returns 'true' when setting the attribute was successful or 'false' when the attribute was not found or the - * attribute is read-only or another error occurred. + * @brief Get the listener configuration. Overload of sdv::app::IAppConnections::GetListenerConfig. + * @param[in] ssName Name of the listener. + * @return String containing the listener configuration. */ - virtual bool Set(/*in*/ const sdv::u8string& ssAttribute, /*in*/ sdv::any_t anyAttribute) override; + sdv::u8string GetListenerConfig(/*in*/ const sdv::u8string& ssName) const override; /** - * @brief Get the attribute flags belonging to a certain attribute. Overload of sdv::IAttributes::GetFlags. - * @param[in] ssAttribute Name of the attribute. - * @return Returns the attribute flags (zero or more EAttributeFlags flags) or 0 when the attribute could not be found. + * @brief Add or update a listener configuration. Overload of sdv::app::IAppConnections::AddListenerConfig. + * @remarks Only accessible when the application runs in maintenance mode. + * @param[in] ssName Name of the listener configuration. + * @param[in] ssConfig The configuration string for the listener. + * @return Returns whether the listener could be added (fails when the listener already exists). */ - virtual uint32_t GetFlags(/*in*/ const sdv::u8string& ssAttribute) const override; + bool AddListenerConfig(/*in*/ const sdv::u8string& ssName, /*in*/ const sdv::u8string& ssConfig) override; + + /** + * @brief Remove a listener configuration with the provided name. Overload of sdv::app::IAppConnections::RemoveListenerConfig. + * @remarks Only accessible when the application runs in maintenance mode. + * @param[in] ssName Name of the listener configuration. + * @return Returns whether the removal was successful. + */ + bool RemoveListenerConfig(/*in*/ const sdv::u8string& ssName) override; + + /** + * @brief Get a sequence with connection names. Overload of sdv::app::IAppConnections::GetConnections. + * @return Sequence with connection name strings. + */ + sdv::sequence GetConnections() const override; + + /** + * @brief Get the connection configuration. Overload of sdv::app::IAppConnections::GetConnectionConfig. + * @param[in] ssName Name of the connection. + * @return String containing the connection configuration. + */ + sdv::u8string GetConnectionConfig(/*in*/ const sdv::u8string& ssName) const override; + + /** + * @brief Add or update a connection configuration. Overload of sdv::app::IAppConnections::AddConnectionConfig. + * @remarks Only accessible when the application runs in maintenance mode. + * @param[in] ssName Name of the connection configuration. + * @param[in] ssConfig The configuration string for the connection. + * @param[in] ssInsertBefore Reference to the string to connection to insert the the new connection before, or empty when the + * the new connection should be placed at the end. + * @return Returns whether the connection could be added (fails when the connection already exists). + */ + bool AddConnectionConfig(/*in*/ const sdv::u8string& ssName, /*in*/ const sdv::u8string& ssConfig, + /*in*/ const sdv::u8string& ssInsertBefore = std::string()) override; + + /** + * @brief Remove a connection configuration with the provided name. Overload of + * sdv::app::IAppConnections::RemoveConnectionConfig. + * @remarks Only accessible when the application runs in maintenance mode. + * @param[in] ssName Name of the connection configuration. + * @return Returns whether the removal was successful. + */ + bool RemoveConnectionConfig(/*in*/ const sdv::u8string& ssName) override; + + /** + * @brief Return the number of retries to establish a connection. Overload of sdv::app::IAppConnections::GetRetries. + * @return Number of retries. + */ + uint32_t GetConnectRetries() const override; /** * @brief Reset the settings after a shutdown. @@ -360,26 +532,42 @@ public: void Reset(); private: - sdv::app::EAppContext m_eContextMode = sdv::app::EAppContext::no_context; ///< The application is running as... - uint32_t m_uiInstanceID = 0u; ///< Instance number. - uint32_t m_uiRetries = 0u; ///< Number of retries to establish a connection. + /** + * @brief Get the default listener config. + * @return String containing the TOML with the listener config. + */ + std::string DefaultListenerConfig() const; + + /** + * @brief Get the default connection config. + * @return String containing the TOML with the connection config. + */ + std::string DefaultConnectionConfig() const; + + sdv::app::EAppContext m_eAppContextType = sdv::app::EAppContext::no_context; ///< The application is running as... + uint32_t m_uiInstanceID = 0u; ///< Instance number (default 1000, but only after startup). + uint32_t m_uiConnectRetries = 5u; ///< Number of retries to establish a connection. std::string m_ssLoggerClass; ///< Class name of a logger service. std::filesystem::path m_pathLoggerModule; ///< Module name of a custom logger. std::string m_ssProgramTag; ///< Program tag to use when logging. sdv::core::ELogSeverity m_eSeverityFilter = sdv::core::ELogSeverity::info; ///< Severity level filter while logging. sdv::core::ELogSeverity m_eSeverityViewFilter = sdv::core::ELogSeverity::error; ///< Severity level filter while logging. - bool m_bSilent = false; ///< When set, no console reporting takes place. - bool m_bVerbose = false; ///< When set, extensive console reporting takes place. + EAppConsoleReporting m_eConsoleReporting = EAppConsoleReporting::normal; ///< Console reporting + bool m_bRedirectMon = false; ///< When set, redirect the message from the monitor onto console. + std::filesystem::path m_pathFrameworkDir; ///< Location of framework component directory. std::filesystem::path m_pathRootDir; ///< Location of user component root directory. std::filesystem::path m_pathInstallDir; ///< Location of user component installations (root with instance). std::filesystem::path m_pathPlatformConfig; ///< The platform configuration from the settings file. std::filesystem::path m_pathVehIfcConfig; ///< The vehicle interface configuration from the settings file. std::filesystem::path m_pathVehAbstrConfig; ///< The vehicle abstraction configuration from the settings file. std::filesystem::path m_pathUserConfig; ///< The user configuration from the settings file. - bool m_bPlatformConfig = false; ///< Platform config was explicitly enabled/disabled. - bool m_bVehIfcConfig = false; ///< Vehicle interface config was explicitly enabled/disabled. - bool m_bVehAbstrConfig = false; ///< Vehicle abstraction config was explicitly enabled/disabled. - bool m_bUserConfig = false; ///< User config was explicitly enabled/disabled. + bool m_bUpdatePlatformConfig = false; ///< Platform config was explicitly marked for update. + bool m_bUpdateVehIfcConfig = false; ///< Vehicle interface config was explicitly marked for update. + bool m_bUpdateVehAbstrConfig = false; ///< Vehicle abstraction config was explicitly marked for update. + bool m_bUpdateUserConfig = false; ///< User config was explicitly marked for update. + std::map m_mapListeners; ///< Map with listener configurations. + std::vector> m_vecConnections; ///< Vector with connection configurations. + std::string m_ssDefaultComProvider; ///< Name of the default communication provider. }; /** @@ -398,7 +586,11 @@ public: // Interface map BEGIN_SDV_INTERFACE_MAP() - SDV_INTERFACE_ENTRY_MEMBER(sdv::IAttributes, GetAppSettings()) + SDV_INTERFACE_SET_SECTION_CONDITION(GetAppSettings().IsMaintenanceApplication(), 1) + SDV_INTERFACE_SECTION(1) + SDV_INTERFACE_ENTRY_MEMBER(sdv::app::IAppSettingsPersist, GetAppSettings()) + SDV_INTERFACE_ENTRY_MEMBER(sdv::app::IAppConnections, GetAppSettings()) + SDV_INTERFACE_DEFAULT_SECTION() END_SDV_INTERFACE_MAP() // Object declarations @@ -406,6 +598,11 @@ public: DECLARE_OBJECT_CLASS_NAME("AppSettingsService") DECLARE_OBJECT_SINGLETON() + // Parameter map + BEGIN_SDV_PARAM_MAP() + SDV_PARAM_CHAIN_MEMBER(GetAppSettings()) + END_SDV_PARAM_MAP() + /** * @brief Get access to the application settings. * @return Returns the one global instance of the application config. diff --git a/sdv_services/core/installation_composer.cpp b/sdv_services/core/installation_composer.cpp index ce518b0..8cf336b 100644 --- a/sdv_services/core/installation_composer.cpp +++ b/sdv_services/core/installation_composer.cpp @@ -38,6 +38,12 @@ #error OS is not supported! #endif +namespace +{ + constexpr float kMaxBLOBSize = (24*1024*1024) + 25000000; +} + + CInstallComposer::~CInstallComposer() {} @@ -1019,10 +1025,16 @@ uint32_t CInstallComposer::SerializeModuleBLOB(uint32_t uiChecksumInit, sdv::poi // Calculate the size of the BLOB (including padding and checksum) sdv::installation::SPackageBLOBChecksum sBLOBChecksum{}; size_t nSize = 0; - sdv::ser_size(sBLOB, nSize); + sdv::ser_size(sBLOB, nSize); sdv::ser_size(sBLOBChecksum, nSize); if (nSize % 8) nSize += 8 - nSize % 8; sBLOB.uiBLOBSize = static_cast(nSize); + if(sBLOB.uiBLOBSize > kMaxBLOBSize) + { + sdv::installation::XPackageSizeExceeded exception; + exception.ssFileName = sBLOB.sFileDesc.ssFileName; + throw exception; + } // Serialize the BLOB sdv::serializer serializer; @@ -1246,7 +1258,7 @@ sdv::installation::SPackageBLOB CInstallComposer::DeserializeBLOB(std::ifstream& // Extend the buffer and read the BLOB data (after the resize, the psPartialBLOB pointer could be invalidated). uint32_t uiBLOBSize = psPartialBLOB->uiBLOBSize; - if (uiBLOBSize > 26*1024*1024) // 26 instead of 24 becasue of door example + if (uiBLOBSize > kMaxBLOBSize) throw sdv::installation::XIncompatiblePackage(); // Safety ptrBLOB.resize(uiBLOBSize); if (!ptrBLOB) diff --git a/sdv_services/core/iso_monitor.cpp b/sdv_services/core/iso_monitor.cpp index e356797..dcf7d5e 100644 --- a/sdv_services/core/iso_monitor.cpp +++ b/sdv_services/core/iso_monitor.cpp @@ -23,11 +23,11 @@ CIsoMonitor::~CIsoMonitor() GetAppControl().RequestShutdown(); } -void CIsoMonitor::Initialize(/*in*/ const sdv::u8string& ssObjectConfig) +void CIsoMonitor::Initialize(/*in*/ const sdv::SObjectInfo& sObjectInfo) { if (m_pObjectControl) { - m_pObjectControl->Initialize(ssObjectConfig); + m_pObjectControl->Initialize(sObjectInfo); m_eObjectState = m_pObjectControl->GetObjectState(); } else diff --git a/sdv_services/core/iso_monitor.h b/sdv_services/core/iso_monitor.h index 1a24393..761c49f 100644 --- a/sdv_services/core/iso_monitor.h +++ b/sdv_services/core/iso_monitor.h @@ -42,9 +42,9 @@ public: /** * @brief Initialize the object. Overload of sdv::IObjectControl::Initialize. - * @param[in] ssObjectConfig Optional configuration string. + * @param[in] sObjectInfo The registration information of this object. */ - virtual void Initialize(/*in*/ const sdv::u8string& ssObjectConfig) override; + virtual void Initialize(/*in*/ const sdv::SObjectInfo& sObjectInfo) override; /** * @brief Get the current state of the object. Overload of sdv::IObjectControl::GetObjectState. diff --git a/sdv_services/core/local_shutdown_request.h b/sdv_services/core/local_shutdown_request.h index 04323c4..6bd515e 100644 --- a/sdv_services/core/local_shutdown_request.h +++ b/sdv_services/core/local_shutdown_request.h @@ -61,6 +61,8 @@ inline bool RequestShutdown(uint32_t uiInstanceID = 1000u) CloseHandle(hEvent); return true; } + else + std::cerr << "ERROR: Shutdown event handle is not available: " << m_ssSignalName << std::endl; #elif defined __unix__ sem_t* pSemaphore = sem_open(m_ssSignalName.c_str(), 0); if (pSemaphore && pSemaphore != SEM_FAILED) diff --git a/sdv_services/core/logger.cpp b/sdv_services/core/logger.cpp index f46337f..b3bd1ca 100644 --- a/sdv_services/core/logger.cpp +++ b/sdv_services/core/logger.cpp @@ -14,6 +14,7 @@ #include "logger.h" #include #include "../../global/exec_dir_helper.h" +#include "app_settings.h" #ifdef __unix__ #include @@ -38,7 +39,7 @@ CLogger::~CLogger() void CLogger::Log(sdv::core::ELogSeverity eSeverity, /*in*/ const sdv::u8string& ssSrcFile, /*in*/ uint32_t uiSrcLine, /*in*/ sdv::process::TProcessID tProcessID, /*in*/ const sdv:: u8string& ssObjectName, /*in*/ const sdv::u8string& ssMessage) { - if (static_cast(eSeverity) >= static_cast(m_eViewFilter)) + if (GetAppSettings().IsConsoleVerbose() && static_cast(eSeverity) >= static_cast(m_eViewFilter)) { if (tProcessID) std::clog << "[PID#" << static_cast(tProcessID) << "] "; if (!ssObjectName.empty()) std::clog << ssObjectName << " "; diff --git a/sdv_services/core/module.cpp b/sdv_services/core/module.cpp index 2f6cf54..827779f 100644 --- a/sdv_services/core/module.cpp +++ b/sdv_services/core/module.cpp @@ -297,6 +297,10 @@ bool CModuleInst::Load(const std::filesystem::path& rpathModule) noexcept } m_mapClassInfo[sClass.ssName] = sClass; + + // Add aliases? + for (const auto& rssAlias : sClass.seqClassAliases) + m_mapClassInfo[rssAlias] = sClass; } } catch (const sdv::toml::XTOMLParseException&) diff --git a/sdv_services/core/module_control.cpp b/sdv_services/core/module_control.cpp index 72f4ed0..636ee70 100644 --- a/sdv_services/core/module_control.cpp +++ b/sdv_services/core/module_control.cpp @@ -250,7 +250,7 @@ std::shared_ptr CModuleControl::FindModuleByClass(const std::string } } - // For main and isolated applications, check whether the module is in one of the installation manifests. + // For main, isolated and maintenance applications, check whether the module is in one of the installation manifests. auto optManifest = GetAppConfig().FindInstalledComponent(rssClass); if (!optManifest) return nullptr; std::filesystem::path pathModule = std::filesystem::u8path(static_cast(optManifest->ssModulePath)); @@ -378,8 +378,6 @@ std::string CModuleControl::SaveConfig(const std::set& /* sdv::core::TModuleID CModuleControl::ContextLoad(const std::filesystem::path& rpathModule, const std::string& rssManifest) { - if (GetAppSettings().IsMaintenanceApplication()) return 0; // Not allowed - // Run through the manifest and check for complex services, applications and utilities. // TODO EVE: Temporary suppression of cppcheck warning. // cppcheck-suppress variableScope @@ -462,7 +460,7 @@ void CModuleControl::AddCurrentPath() if (!m_lstSearchPaths.empty()) return; // Add the core directory - std::filesystem::path pathCoreDir = GetCoreDirectory().lexically_normal(); + std::filesystem::path pathCoreDir = GetAppSettings().GetFrameworkDir().lexically_normal(); m_lstSearchPaths.push_back(pathCoreDir); // Add the exe dir diff --git a/sdv_services/core/permission_control.cpp b/sdv_services/core/permission_control.cpp new file mode 100644 index 0000000..5687a44 --- /dev/null +++ b/sdv_services/core/permission_control.cpp @@ -0,0 +1,167 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#include "permission_control.h" + +#if defined __GNUC__ && defined _WIN32 +thread_local std::list>& + CPermissionControl::m_lstPermissionTracker = CreatePermissionList(); +thread_local std::map& + CPermissionControl::m_mapPermissions = CreatePermissionMap(); +#else +thread_local std::list> CPermissionControl::m_lstPermissionTracker; +thread_local std::map CPermissionControl::m_mapPermissions; +#endif + +CPermissionControl& GetPermissionControl() +{ + static CPermissionControl control; + return control; +} + +CAccessPermission::CAccessPermission(CPermissionControl& rPermissionControl, sdv::core::EAccessPermission ePermission) : + m_rPermissionControl(rPermissionControl), m_ePermission(ePermission) +{ + if (m_ePermission != sdv::core::EAccessPermission::not_set) + rPermissionControl.IncrementPermissionCount(ePermission); +} + +CAccessPermission::CAccessPermission(const CAccessPermission& rPermission) : + m_rPermissionControl(rPermission.m_rPermissionControl), m_ePermission(rPermission.m_ePermission) +{ + if (m_ePermission != sdv::core::EAccessPermission::not_set) + m_rPermissionControl.IncrementPermissionCount(m_ePermission); +} + +CAccessPermission::CAccessPermission(CAccessPermission&& rPermission) : + m_rPermissionControl(rPermission.m_rPermissionControl), m_ePermission(rPermission.m_ePermission) +{ + rPermission.m_ePermission = sdv::core::EAccessPermission::not_set; +} + +CAccessPermission& CAccessPermission::operator=(const CAccessPermission& rPermission) +{ + // Remove current permission and copy the new permission + if (m_ePermission != sdv::core::EAccessPermission::not_set) + m_rPermissionControl.DecrementPermissionCount(m_ePermission); + m_ePermission = rPermission.m_ePermission; + if (m_ePermission != sdv::core::EAccessPermission::not_set) + m_rPermissionControl.IncrementPermissionCount(m_ePermission); + return *this; +} + +CAccessPermission& CAccessPermission::operator=(CAccessPermission && rPermission) +{ + // Remove current permission and move the new permission + if (m_ePermission != sdv::core::EAccessPermission::not_set) + m_rPermissionControl.DecrementPermissionCount(m_ePermission); + m_ePermission = rPermission.m_ePermission; + rPermission.m_ePermission = sdv::core::EAccessPermission::not_set; + return *this; +} + +CAccessPermission ::~CAccessPermission() +{ + if (m_ePermission != sdv::core::EAccessPermission::not_set) + m_rPermissionControl.DecrementPermissionCount(m_ePermission); +} + +sdv::core::EAccessPermission CAccessPermission::Permission() const +{ + return m_ePermission; +} + +sdv::core::TPermissionID CPermissionControl::RestrictAccessPermission(/*in*/ sdv::core::EAccessPermission ePermission) +{ + // Unlike the CreateAccessPermission this function can only be used to restrict an already available restriction. It cannot be + // used to set the initial restriction. + if (m_lstPermissionTracker.empty()) return false; + + sdv::core::TPermissionID tPermissionID = m_idgen.Generate(); + if (!tPermissionID) return 0u; + m_mapPermissions.emplace(tPermissionID, CreatePermissionObject(ePermission)); + return tPermissionID; +} + +bool CPermissionControl::ReleaseAccessPermission(/*in*/ sdv::core::TPermissionID tPermissionID) +{ + auto itPermission = m_mapPermissions.find(tPermissionID); + if (itPermission == m_mapPermissions.end()) return false; + m_mapPermissions.erase(itPermission); + return true; +} + +sdv::core::TPermissionTransferID CPermissionControl::TransferCurrentPermission() +{ + std::unique_lock lock(m_mtxPermissionTransfer); + sdv::core::TPermissionTransferID tTransferID = m_idgen.Generate(); + m_mapPermissionTransfer[tTransferID] = GetCurrentPermission(); + return tTransferID; +} + +sdv::core::TPermissionID CPermissionControl::SetAccessPermission(/*in*/ sdv::core::TPermissionTransferID tTransferID) +{ + // Get the access restriction level from the transfer permission map. + std::unique_lock lock(m_mtxPermissionTransfer); + auto itTransferPermission = m_mapPermissionTransfer.find(tTransferID); + if (itTransferPermission == m_mapPermissionTransfer.end()) return 0u; + sdv::core::EAccessPermission ePermission = itTransferPermission->second; + m_mapPermissionTransfer.erase(itTransferPermission); + + // Unlike the RestrictAccessPermission function, the SetAccessPermission function can be used to set an initial permission. + sdv::core::TPermissionID tPermissionID = m_idgen.Generate(); + if (!tPermissionID) return 0u; + m_mapPermissions.emplace(tPermissionID, CreatePermissionObject(ePermission)); + return tPermissionID; +} + +sdv::core::EAccessPermission CPermissionControl::GetCurrentPermission() const +{ + return m_lstPermissionTracker.empty() ? sdv::core::EAccessPermission::restricted_access : m_lstPermissionTracker.front().first; +} + +CAccessPermission CPermissionControl::CreatePermissionObject(sdv::core::EAccessPermission ePermission) +{ + sdv::core::EAccessPermission ePermissionLocal = std::max(ePermission, sdv::core::EAccessPermission::restricted_access); + return CAccessPermission(*this, ePermissionLocal); +} + +void CPermissionControl::IncrementPermissionCount(sdv::core::EAccessPermission ePermission) +{ + // In the permission tracker list, search for the counter for this permission or, if not existing, the next higher permission. + auto itPermissionCounter = m_lstPermissionTracker.begin(); + for (; itPermissionCounter != m_lstPermissionTracker.end(); ++itPermissionCounter) + { + if (itPermissionCounter->first >= ePermission) + break; + } + + // If counter doesn't exist, insert a new counter. + if (itPermissionCounter == m_lstPermissionTracker.end() || itPermissionCounter->first > ePermission) + itPermissionCounter = m_lstPermissionTracker.insert(itPermissionCounter, std::make_pair(ePermission, 1)); + else // exists, increase the counter + itPermissionCounter->second++; +} + +void CPermissionControl::DecrementPermissionCount(sdv::core::EAccessPermission ePermission) +{ + // Find the permission counter + auto itPermissionCounter = std::find_if(m_lstPermissionTracker.begin(), m_lstPermissionTracker.end(), [&](const auto& rprPermissionCount) + { return rprPermissionCount.first == ePermission; }); + if (itPermissionCounter == m_lstPermissionTracker.end()) return; + + // Reduce the counter and if reaching zero, erase the counter + itPermissionCounter->second--; + if (!itPermissionCounter->second) + m_lstPermissionTracker.erase(itPermissionCounter); +} diff --git a/sdv_services/core/permission_control.h b/sdv_services/core/permission_control.h new file mode 100644 index 0000000..177b267 --- /dev/null +++ b/sdv_services/core/permission_control.h @@ -0,0 +1,263 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#ifndef PERMISSION_CONTROL_H +#define PERMISSION_CONTROL_H + +#include +#include +#include + +#include "../../global/unique_id.h" + +// Forward declaration +class CPermissionControl; + +/** + * @brief Use a list to create a stack, which allows elements to be deleted. + */ +using TPermissionStack = std::list; + +/** + * @brief Permission class for the requested access. This class manages the lifetime of the permissions. + */ +class CAccessPermission +{ +private: + /// Permission control is allowed to create an access permission object. + friend CPermissionControl; + + /** + * @brief Constructor + * @param[in] rPermissionControl Reference to permission control. + * @param[in] ePermission The requested access permission. + */ + CAccessPermission(CPermissionControl& rPermissionControl, sdv::core::EAccessPermission ePermission); + +public: + /** + * @brief Destructor + */ + ~CAccessPermission(); + + /** + * @brief Copy constructor + * @param[in] rPermission Reference to the permission object to copy from. + */ + CAccessPermission(const CAccessPermission& rPermission); + + /** + * @brief Move constructor. + * @param[in] rPermission Reference to the permission object to move from. + */ + CAccessPermission(CAccessPermission&& rPermission); + + /** + * @brief Copy assignment operator. + * @param[in] rPermission Reference to the permission object to move from. + * @return Reference to this access permission object. + */ + CAccessPermission& operator=(const CAccessPermission& rPermission); + + /** + * @brief Move assignment operator. + * @param[in] rPermission Reference to the permission object to move from. + * @return Reference to this access permission object. + */ + CAccessPermission& operator=(CAccessPermission&& rPermission); + + /** + * @brief Get the permission level assigned to this object. + * @remarks This might not correspond to the current permission level available for this thread. + * @return The permission level. + */ + sdv::core::EAccessPermission Permission() const; + +private: + CPermissionControl& m_rPermissionControl; ///< Reference to the permission control. + sdv::core::EAccessPermission m_ePermission; ///< Requested permission. +}; + +/** + * @brief Permission control provides a simple access mechanism to allow the management of access to system functions. + */ +class CPermissionControl : public sdv::IInterfaceAccess, public sdv::core::IPermissionControl +{ +public: + /// Access permission class can access the permission control. + friend CAccessPermission; + + /** + * @brief Default constructor + */ + CPermissionControl() = default; + + // Interface map + BEGIN_SDV_INTERFACE_MAP() + SDV_INTERFACE_ENTRY(sdv::core::IPermissionControl) + END_SDV_INTERFACE_MAP() + + /** + * @brief Restrict the access permission for the current thread. Overload of + * sdv::core.:IPermissionControl::RestrictAccessPermission. + * @remarks The access restriction will be assigned to the current thread and combined with previous and future + * permissions. The lowest assigned permission will determine the actual access permission for the current thread. + * @remarks The access restriction will stay in effect until it is released by the function ReleaseAccessPermission. + * @remarks A newly created thread has fully restricted access. This cannot be changed using this function. Use an access + * restriction transfer from one thread to this thread to set a higher level of access permission. + * @param[in] ePermission The permission to restrict to. + * @return The permission ID for this restriction or 0 when the access permission could not be set. Use the + * ReleaseAccessPermission to release the restriction again. + */ + virtual sdv::core::TPermissionID RestrictAccessPermission(/*in*/ sdv::core::EAccessPermission ePermission) override; + + /** + * @brief Release a previously set access restriction for the current thread. Overload of + * sdv::core::IPermissionControl::ReleaseAccessPermission. + * @param[in] tPermissionID The ID of the access restriction previously set for the current thread. + * @return Returns whether the restriction could be released successfully. + */ + virtual bool ReleaseAccessPermission(/*in*/ sdv::core::TPermissionID tPermissionID) override; + + /** + * @brief Prepare to transfer the access restriction from the current thread.Overload of + * sdv::core.:IPermissionControl::TransferCurrentPermission. + * @return The ID of the transfer object containing the current access permissions or 0 when the transfer preparation has + * failed. + */ + virtual sdv::core::TPermissionTransferID TransferCurrentPermission() override; + + /** + * @brief Set the access permission using a transfer object from one thread to another. Overload of + * sdv::core.:IPermissionControl::SetAccessPermission. + * @remarks An new thread has fully restricted access per default. Use this function to set the required access level. If a + * thread has already initialized with the proper access level, this function will set identical or lower access permissions + * for the current thread. + * @param[in] tTransferID The IS of the prepared access permission transfer. + * @return The permission ID for this restriction or 0 when the access permission could not be transferred. Use the + * ReleaseAccessPermission to release the restriction again. + */ + virtual sdv::core::TPermissionID SetAccessPermission(/*in*/ sdv::core::TPermissionTransferID tTransferID) override; + + /** + * @brief Get the access permission level for the current thread. This will be the lowest restriction set for the current + * thread. Overload of sdv::core.:IPermissionControl::GetCurrentPermission. + * @return The current access permission level. + */ + virtual sdv::core::EAccessPermission GetCurrentPermission() const override; + + /** + * @brief Create a permission object that assigns the access permissions to the call thread. The permissions stay in place + * until a last copy of the object is destroyed. + * @param[in] ePermission The new permission to set for the thread. If the permission has a value smaller than + * sdv::core::EAccessPermission::restricted_access, the object will be created with restricted access permission. + * @return The permission object. The permission will stay in place during the lifetime of the object. + */ + CAccessPermission CreatePermissionObject(sdv::core::EAccessPermission ePermission); + +private: + /** + * @brief Increment the counter for a specific permission. + * @param[in] ePermission The permission the count should be incremented for. + */ + void IncrementPermissionCount(sdv::core::EAccessPermission ePermission); + + /** + * @brief Increment the counter for a specific permission. + * @param[in] ePermission The permission the count should be incremented for. + */ + void DecrementPermissionCount(sdv::core::EAccessPermission ePermission); + + CUniqueID m_idgen; ///< Unique ID generator. + +#if defined __GNUC__ && defined _WIN32 + // https://dev.azure.com/SW4ZF/AZP-431_DivDI_Vehicle_API/_workitems/edit/610009 + // In MINGW GCC implementation of thread_local, there is an issue with complex classes being used after the destruction. This + // is caused by an architectural bug (https://github.com/msys2/MINGW-packages/issues/2519) causing the emutls (GCC Emulated TLS) + // managing the memory allocation to be executed before the __cxa_thread_exit, the thread cleanup callback of Windows. + // The solution is to use trivial data only (pointer, reference, integers, etc.). A workaround is implemented to use a reference + // of the permission list and map instead of an instance to the list and map. + + /** + * @brief Create a permission list object. + * @return Reference to the created list object. + */ + static std::list>& CreatePermissionList() + { + static std::mutex mtxPermissionLists; + static std::list>> lstPermissionLists; + std::unique_lock lock(mtxPermissionLists); + lstPermissionLists.resize(lstPermissionLists.size() + 1); + return lstPermissionLists.back(); + } + + /// Thread specific permission tracker list (permission, lowest first and the count for each permission second). + static thread_local std::list>& m_lstPermissionTracker; + + /** + * @brief Create a permission map object. + * @return Reference to the created map object. + */ + static std::map& CreatePermissionMap() + { + static std::mutex mtxPermissionMaps; + static std::list> lstPermissionMaps; + std::unique_lock lock(mtxPermissionMaps); + lstPermissionMaps.resize(lstPermissionMaps.size() + 1); + return lstPermissionMaps.back(); + } + + /// Thread specific permission access map. + static thread_local std::map& m_mapPermissions; +#else + /// Thread specific permission tracker list (permission, lowest first and the count for each permission second). + static thread_local std::list> m_lstPermissionTracker; + + /// Thread specific permission access map. + static thread_local std::map m_mapPermissions; +#endif + + /// Permission transfer object map (allow shifting permission from one thread to another). + std::map m_mapPermissionTransfer; + + std::mutex m_mtxPermissionTransfer; ///< Protecting the permission transfer map. +}; + +/** + * @brief Return the permission control. + * @return Reference to the permission control. + */ +CPermissionControl& GetPermissionControl(); + +/** + * @brief Permission control service + */ +class CPermissionControlService : public sdv::CSdvObject +{ +public: + CPermissionControlService() = default; + + // Interface map + BEGIN_SDV_INTERFACE_MAP() + SDV_INTERFACE_CHAIN_MEMBER(GetPermissionControl()) + END_SDV_INTERFACE_MAP() + + // Object declarations + DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::system_object) + DECLARE_OBJECT_CLASS_NAME("PermissionControlService") + DECLARE_OBJECT_SINGLETON() +}; + +DEFINE_SDV_OBJECT(CPermissionControlService) + +#endif // !defined PERMISSION_CONTROL_H \ No newline at end of file diff --git a/sdv_services/core/repository.cpp b/sdv_services/core/repository.cpp index 62df231..f0deacb 100644 --- a/sdv_services/core/repository.cpp +++ b/sdv_services/core/repository.cpp @@ -16,12 +16,14 @@ #include #include #include +#include #include "object_lifetime_control.h" #include "../../global/base64.h" #include "module_control.h" #include "app_config.h" #include "app_control.h" #include "app_settings.h" +#include "permission_control.h" // GetRepository might be redirected for unit tests. #ifndef GetRepository @@ -60,39 +62,154 @@ void CRepository::SetRunningMode() sdv::IInterfaceAccess* CRepository::GetObject(const sdv::u8string& ssObjectName) { + auto eAccessPermission = GetPermissionControl().GetCurrentPermission(); + if (eAccessPermission < sdv::core::EAccessPermission::remote_access) + return nullptr; + std::shared_lock lock(m_mtxObjects); auto itService = m_mapServiceObjects.find(ssObjectName); - if (itService != m_mapServiceObjects.end()) return (*itService->second)->ptrObject; + if (itService != m_mapServiceObjects.end()) + { + // Check access permissions + switch ((*itService->second)->sClassInfo.eType) + { + case sdv::EObjectType::complex_service: + case sdv::EObjectType::vehicle_function: + case sdv::EObjectType::basic_service: + case sdv::EObjectType::sensor: + case sdv::EObjectType::actuator: + // Allowed for remote access or higher + //if (eAccessPermission < sdv::core::EAccessPermission::remote_access) + // return nullptr; + break; + case sdv::EObjectType::system_object: + // Allowed for local access or higher + if (eAccessPermission < sdv::core::EAccessPermission::local_access) + return nullptr; + break; + default: + // Allowed for full access only + if (eAccessPermission != sdv::core::EAccessPermission::full_access) + return nullptr; + } + return (*itService->second)->ptrObject; + } lock.unlock(); // In case the object is not in the service map and this is a main or isolated application, create the object if the object is - // known in the installation manifest and is a system object. + // known in the installation manifest. auto optManifest = GetAppConfig().FindInstalledComponent(ssObjectName); - if (optManifest && optManifest->eType == sdv::EObjectType::system_object) - return GetObjectByID(CreateObject(optManifest->ssName, optManifest->ssDefaultObjectName, "")); + if (optManifest) + { + // Check access permissions + bool bAutoCreate = false; + switch (optManifest->eType) + { + case sdv::EObjectType::complex_service: + case sdv::EObjectType::vehicle_function: + case sdv::EObjectType::basic_service: + case sdv::EObjectType::sensor: + case sdv::EObjectType::actuator: + // Automatic component creation only for main application. + if (!GetAppSettings().IsMainApplication()) break; + bAutoCreate = true; + + // Allowed for remote access or higher + if (eAccessPermission < sdv::core::EAccessPermission::remote_access) + return nullptr; + break; + case sdv::EObjectType::system_object: + // Automatic component creation allowed for system objects. The objects themselves have to block in case of unwanted + // access by external or isolated applications. + bAutoCreate = true; - // Forward the request to core repository if one is linked here (this can only occur with isolated and external applications). - if (!m_ptrCoreRepoAccess) return nullptr; - sdv::core::IObjectAccess* pObjectAccess = m_ptrCoreRepoAccess.GetInterface(); - if (!pObjectAccess) return nullptr; - return pObjectAccess->GetObject(ssObjectName); + // Allowed for local access or higher + if (eAccessPermission < sdv::core::EAccessPermission::local_access) + return nullptr; + break; + default: + // Automatic component creation only for main application. + if (!GetAppSettings().IsMainApplication()) + break; + bAutoCreate = true; + + // Allowed for full access only + if (eAccessPermission != sdv::core::EAccessPermission::full_access) + return nullptr; + } + + // Create the object if automatic creation is enabled + if (bAutoCreate) + return GetObjectByID(CreateObject2(optManifest->ssName, optManifest->ssDefaultObjectName, "", true)); + } + + // Copy the linked repository and then unlock; could cause a deadlock instead. + lock.lock(); + auto lstCoreRepoAccessCopy = m_lstCoreRepoAccess; + lock.unlock(); + + // If there is a linked core, use the core to get ther object + for (auto& rprLink : lstCoreRepoAccessCopy) + { + sdv::core::IObjectAccess* pObjectAccess = rprLink.second.GetInterface(); + if (!pObjectAccess) continue; + auto* pObject = pObjectAccess->GetObject(ssObjectName); + if (pObject) return pObject; + } + return nullptr; } sdv::IInterfaceAccess* CRepository::GetObjectByID(/*in*/ sdv::core::TObjectID tObjectID) { + auto eAccessPermission = GetPermissionControl().GetCurrentPermission(); + if (eAccessPermission < sdv::core::EAccessPermission::remote_access) + return nullptr; + // Only controlled objects are allowed to be returned using GetObjectByID. std::shared_lock lock(m_mtxObjects); auto itObject = m_mapObjects.find(tObjectID); if (itObject != m_mapObjects.end()) - return itObject->second->bControlled ? itObject->second->ptrObject : nullptr; + { + // Check access permissions + switch (itObject->second->sClassInfo.eType) + { + case sdv::EObjectType::complex_service: + case sdv::EObjectType::vehicle_function: + case sdv::EObjectType::basic_service: + case sdv::EObjectType::sensor: + case sdv::EObjectType::actuator: + //// Allowed for remote access or higher + //if (eAccessPermission < sdv::core::EAccessPermission::remote_access) + // return nullptr; + break; + case sdv::EObjectType::system_object: + // Allowed for local access or higher + if (eAccessPermission < sdv::core::EAccessPermission::local_access) + return nullptr; + break; + default: + // Allowed for full access only + if (eAccessPermission != sdv::core::EAccessPermission::full_access) + return nullptr; + } + return itObject->second->bControlled ? itObject->second->ptrObject : nullptr; + } // TODO: Deal with overlapping IDs in this and in core process... - // Forward the request to core repository if one is linked here (this can only occur with isolated and external applications). - if (!m_ptrCoreRepoAccess) return nullptr; - sdv::core::IObjectAccess* pObjectAccess = m_ptrCoreRepoAccess.GetInterface(); - if (!pObjectAccess) return nullptr; - return pObjectAccess->GetObjectByID(tObjectID); + // Copy the linked repository and then unlock; could cause a deadlock instead. + auto lstCoreRepoAccessCopy = m_lstCoreRepoAccess; + lock.unlock(); + + // If there is a linked core, use the core to get ther object + for (auto& rprLink : lstCoreRepoAccessCopy) + { + sdv::core::IObjectAccess* pObjectAccess = rprLink.second.GetInterface(); + if (!pObjectAccess) continue; + auto* pObject = pObjectAccess->GetObjectByID(tObjectID); + if (pObject) return pObject; + } + return nullptr; } sdv::IInterfaceAccess* CRepository::CreateUtility(/*in*/ const sdv::u8string& ssClassName, /*in*/ const sdv::u8string& ssObjectConfig) @@ -101,6 +218,11 @@ sdv::IInterfaceAccess* CRepository::CreateUtility(/*in*/ const sdv::u8string& ss std::unique_lock lock(m_mtxObjects); + // TODO EVE: Currently only allowed for local access. When utilities are instantiated using isolation, also allowed for remote + // access. + if (GetPermissionControl().GetCurrentPermission() < sdv::core::EAccessPermission::local_access) + return nullptr; + // Get a fitting module instance std::shared_ptr ptrModule = GetModuleControl().FindModuleByClass(ssClassName); if (!ptrModule) @@ -161,7 +283,7 @@ sdv::IInterfaceAccess* CRepository::CreateUtility(/*in*/ const sdv::u8string& ss auto* pObjectControl = ptrObject.GetInterface(); if (pObjectControl) { - pObjectControl->Initialize(ssObjectConfig); + pObjectControl->Initialize(*ptrObjectEntry); if (pObjectControl->GetObjectState() != sdv::EObjectState::initialized) { // Destroy the object @@ -193,22 +315,23 @@ sdv::IInterfaceAccess* CRepository::CreateProxyObject(/*in*/ sdv::interface_id i // Get a fitting module instance std::string ssClassName = "Proxy_" + std::to_string(id); std::shared_ptr ptrModule = GetModuleControl().FindModuleByClass(ssClassName); - if (!ptrModule && m_ptrCoreRepoAccess) - { - // Request the server for the name of the module. - const sdv::core::IRepositoryInfo* pRepInfo = m_ptrCoreRepoAccess.GetInterface(); - if (pRepInfo) - { - std::string ssModuleName = - std::filesystem::u8path(static_cast(pRepInfo->FindClass(ssClassName).ssModulePath)). - filename().u8string(); - if (!ssModuleName.empty()) - { - sdv::core::TModuleID tModule = GetModuleControl().Load(ssModuleName); - if (tModule) ptrModule = GetModuleControl().GetModule(tModule); - } - } - } + // TODO EVE + //if (!ptrModule && m_ptrCoreRepoAccess) + //{ + // // Request the server for the name of the module. + // const sdv::core::IRepositoryInfo* pRepInfo = m_ptrCoreRepoAccess.GetInterface(); + // if (pRepInfo) + // { + // std::string ssModuleName = + // std::filesystem::u8path(static_cast(pRepInfo->FindClass(ssClassName).ssModulePath)). + // filename().u8string(); + // if (!ssModuleName.empty()) + // { + // sdv::core::TModuleID tModule = GetModuleControl().Load(ssModuleName); + // if (tModule) ptrModule = GetModuleControl().GetModule(tModule); + // } + // } + //} if (!ptrModule) { SDV_LOG_ERROR("Object creation requested but object class was not found \"", ssClassName, "\"!"); @@ -270,7 +393,7 @@ sdv::IInterfaceAccess* CRepository::CreateProxyObject(/*in*/ sdv::interface_id i auto* pObjectControl = ptrObject.GetInterface(); if (pObjectControl) { - pObjectControl->Initialize(""); + pObjectControl->Initialize(*ptrObjectEntry); if (pObjectControl->GetObjectState() != sdv::EObjectState::initialized) { // Destroy the object @@ -302,22 +425,23 @@ sdv::IInterfaceAccess* CRepository::CreateStubObject(/*in*/ sdv::interface_id id // Get a fitting module instance std::string ssClassName = "Stub_" + std::to_string(id); std::shared_ptr ptrModule = GetModuleControl().FindModuleByClass(ssClassName); - if (!ptrModule && m_ptrCoreRepoAccess) - { - // Request the server for the name of the module. - const sdv::core::IRepositoryInfo* pRepInfo = m_ptrCoreRepoAccess.GetInterface(); - if (pRepInfo) - { - std::string ssModuleName = - std::filesystem::u8path(static_cast(pRepInfo->FindClass(ssClassName).ssModulePath)). - filename().u8string(); - if (!ssModuleName.empty()) - { - sdv::core::TModuleID tModule = GetModuleControl().Load(ssModuleName); - if (tModule) ptrModule = GetModuleControl().GetModule(tModule); - } - } - } + // TODO EVE + //if (!ptrModule && m_ptrCoreRepoAccess) + //{ + // // Request the server for the name of the module. + // const sdv::core::IRepositoryInfo* pRepInfo = m_ptrCoreRepoAccess.GetInterface(); + // if (pRepInfo) + // { + // std::string ssModuleName = + // std::filesystem::u8path(static_cast(pRepInfo->FindClass(ssClassName).ssModulePath)). + // filename().u8string(); + // if (!ssModuleName.empty()) + // { + // sdv::core::TModuleID tModule = GetModuleControl().Load(ssModuleName); + // if (tModule) ptrModule = GetModuleControl().GetModule(tModule); + // } + // } + //} if (!ptrModule) { SDV_LOG_ERROR("Object creation requested but object class was not found \"", ssClassName, "\"!"); @@ -378,7 +502,7 @@ sdv::IInterfaceAccess* CRepository::CreateStubObject(/*in*/ sdv::interface_id id auto* pObjectControl = ptrObject.GetInterface(); if (pObjectControl) { - pObjectControl->Initialize(""); + pObjectControl->Initialize(*ptrObjectEntry); if (pObjectControl->GetObjectState() != sdv::EObjectState::initialized) { // Destroy the object @@ -414,11 +538,11 @@ sdv::core::TObjectID CRepository::CreateObject(const sdv::u8string& ssClassName, Build dependency list and do these checks on dependent objects as well */ - return CreateObject2(ssClassName, ssObjectName, ssObjectConfig); + return CreateObject2(ssClassName, ssObjectName, ssObjectConfig, false); } sdv::core::TObjectID CRepository::CreateObject2(const sdv::u8string& ssClassName, const sdv::u8string& ssObjectName, - const sdv::u8string& ssObjectConfig) + const sdv::u8string& ssObjectConfig, bool bIndirect) { // TODO EVE: Link to core repo. Allow only creation of one object if not already created before... // Add support for automatic app control shutdown when utility is closed in case of isolated app. @@ -457,21 +581,29 @@ sdv::core::TObjectID CRepository::CreateObject2(const sdv::u8string& ssClassName switch (optClassInfo->eType) { case sdv::EObjectType::system_object: + // Only allowed with full access + bError = !bIndirect && (GetPermissionControl().GetCurrentPermission() < sdv::core::EAccessPermission::full_access); break; case sdv::EObjectType::device: case sdv::EObjectType::platform_abstraction: case sdv::EObjectType::vehicle_bus: - bError = !bDeviceAndBasicServiceAllowed; + // Only allowed with full access + bError = !bIndirect && (GetPermissionControl().GetCurrentPermission() < sdv::core::EAccessPermission::full_access); + bError |= !bDeviceAndBasicServiceAllowed; break; case sdv::EObjectType::basic_service: case sdv::EObjectType::sensor: case sdv::EObjectType::actuator: - bError = !bDeviceAndBasicServiceAllowed; + // Only allowed with full access + bError = !bIndirect && (GetPermissionControl().GetCurrentPermission() < sdv::core::EAccessPermission::full_access); + bError |= !bDeviceAndBasicServiceAllowed; break; case sdv::EObjectType::complex_service: case sdv::EObjectType::vehicle_function: + // Only allowed with local access + bError = !bIndirect && (GetPermissionControl().GetCurrentPermission() < sdv::core::EAccessPermission::local_access); bIsolate = GetAppSettings().IsMainApplication(); - bError = !bComplexServiceAllowed; + bError |= !bComplexServiceAllowed; m_bIsoObjectLoaded = true; break; default: @@ -498,8 +630,11 @@ sdv::core::TObjectID CRepository::CreateObject2(const sdv::u8string& ssClassName auto itPreviousService = m_mapServiceObjects.find(ssObjectName2); if (itPreviousService != m_mapServiceObjects.end()) { + // Get the service pointer + std::shared_ptr ptrService = *itPreviousService->second; + // Object entry is valid? - if (!*itPreviousService->second) + if (!ptrService) { // This should not occur... there is a previous object in the service map, but the object is empty. SDV_LOG_ERROR("Object creation requested for class \"", ssClassName, "\", but object with the same name \"", @@ -508,12 +643,15 @@ sdv::core::TObjectID CRepository::CreateObject2(const sdv::u8string& ssClassName } // Trying to create an object with the same name is not an error if the classes are identical. - if ((*itPreviousService->second)->sClassInfo.ssName == ssClassName) - return (*itPreviousService->second)->tObjectID; + const auto& rsClassInfo = ptrService->sClassInfo; + if (rsClassInfo.ssName == ssClassName || + std::find(rsClassInfo.seqClassAliases.begin(), rsClassInfo.seqClassAliases.end(), ssClassName) != + rsClassInfo.seqClassAliases.end()) + return ptrService->tObjectID; // Object name was already used by another class. This is an error. SDV_LOG_ERROR("Object creation requested for class \"", ssClassName, "\", but object with the same name \"", - ssObjectName2, "\" was already instantiated for class \"", (*itPreviousService->second)->sClassInfo.ssName, + ssObjectName2, "\" was already instantiated for class \"", rsClassInfo.ssName, "\"!"); return 0; } @@ -549,14 +687,18 @@ sdv::core::TObjectID CRepository::CreateObject2(const sdv::u8string& ssClassName sdv::core::TObjectID CRepository::CreateObjectFromModule(/*in*/ sdv::core::TModuleID tModuleID, /*in*/ const sdv::u8string& ssClassName, /*in*/ const sdv::u8string& ssObjectName, /*in*/ const sdv::u8string& ssObjectConfig) { - if(ssClassName.empty()) return false; + if(ssClassName.empty()) return 0u; + + // Only allowed with full access + if (GetPermissionControl().GetCurrentPermission() != sdv::core::EAccessPermission::full_access) + return 0u; // Get a fitting module instance std::shared_ptr ptrModule = GetModuleControl().GetModule(tModuleID); if (!ptrModule) { SDV_LOG_ERROR("Object creation requested but object class was not found \"", ssClassName, "\"!"); - return false; + return 0u; } // Check the class type @@ -564,7 +706,7 @@ sdv::core::TObjectID CRepository::CreateObjectFromModule(/*in*/ sdv::core::TModu if (!optClassInfo) { SDV_LOG_ERROR("Object creation requested but object class was not found \"", ssClassName, "\"!"); - return false; + return 0u; } bool bError = false; bool bDeviceAndBasicServiceAllowed = GetAppSettings().IsMainApplication() || GetAppSettings().IsStandaloneApplication() || @@ -602,7 +744,7 @@ sdv::core::TObjectID CRepository::CreateObjectFromModule(/*in*/ sdv::core::TModu // Utilities and marshall objects cannot be created using the CreateObject function. SDV_LOG_ERROR("Creation of an object of invalid type \"", sdv::ObjectType2String(optClassInfo->eType), "\" requested for class \"", ssClassName, "\"!"); - return 0; + return 0u; } // Check for an object name. If not existing get the default name (being either one specified by the object or the class name). @@ -626,7 +768,7 @@ sdv::core::TObjectID CRepository::CreateObjectFromModule(/*in*/ sdv::core::TModu "\", but object with the same name \"", ssObjectName2, "\" was already instantiated, but cannot be found!"); - return 0; + return 0u; } // Trying to create an object with the same name is not an error if the classes are identical. @@ -641,7 +783,7 @@ sdv::core::TObjectID CRepository::CreateObjectFromModule(/*in*/ sdv::core::TModu "\" was already instantiated for class \"", (*itPreviousService->second)->sClassInfo.ssName, "\"!"); - return 0; + return 0u; } // Check with singleton objects if the object was already instantiated. @@ -657,7 +799,7 @@ sdv::core::TObjectID CRepository::CreateObjectFromModule(/*in*/ sdv::core::TModu SDV_LOG_ERROR("Object creation requested but object from the same class \"", ssClassName, "\" was already instantiated and only one instance is allowed!"); - return 0; + return 0u; } } } @@ -671,17 +813,41 @@ sdv::core::TObjectID CRepository::CreateObjectFromModule(/*in*/ sdv::core::TModu bool CRepository::DestroyObject(/*in*/ const sdv::u8string& ssObjectName) { - return DestroyObject2(ssObjectName); + TDeferredObjectDestructionList lstDeferredObjectDestruction; + bool bRes = DestroyObject2(ssObjectName, false, lstDeferredObjectDestruction); + for (auto& rprObject : lstDeferredObjectDestruction) + rprObject.first->ptrModule->DestroyObject(rprObject.second); + return bRes; } -bool CRepository::DestroyObject2(/*in*/ const sdv::u8string& ssObjectName) +bool CRepository::DestroyObject2(const sdv::u8string& ssObjectName, bool bIndirect, + TDeferredObjectDestructionList& rlstDeferredObjectDestruction) { std::unique_lock lock(m_mtxObjects); auto itService = m_mapServiceObjects.find(ssObjectName); if (itService == m_mapServiceObjects.end()) return false; + auto ptrObjectEntry = *itService->second; + + // When directly called, check the access permissions + if (!bIndirect) + { + switch (ptrObjectEntry->sClassInfo.eType) + { + case sdv::EObjectType::complex_service: + case sdv::EObjectType::vehicle_function: + // Only allowed having local or higher access permission + if (GetPermissionControl().GetCurrentPermission() < sdv::core::EAccessPermission::local_access) + return false; + break; + default: + // Only allowed having full access permission + if (GetPermissionControl().GetCurrentPermission() != sdv::core::EAccessPermission::full_access) + return false; + break; + } + } // Print info - auto ptrObjectEntry = *itService->second; if (GetAppSettings().IsConsoleVerbose()) std::cout << "Destroy a " << sdv::ObjectType2String(ptrObjectEntry->sClassInfo.eType) << " #" << ptrObjectEntry->tObjectID << " of type " << ptrObjectEntry->sClassInfo.ssName << " with the name " << ssObjectName << std::endl; @@ -717,13 +883,25 @@ bool CRepository::DestroyObject2(/*in*/ const sdv::u8string& ssObjectName) lock.unlock(); // Only controlled objects can be destroyed this way. - if (ptrDependingObject->bControlled) DestroyObject(ptrDependingObject->ssName); + if (ptrDependingObject->bControlled) DestroyObject2(ptrDependingObject->ssName, false, rlstDeferredObjectDestruction); } }; + + // Static dependance from object class fnDestroyDependingObjects(GetDependingObjectInstancesByClass(ptrObjectEntry->sClassInfo.ssName)); if (ptrObjectEntry->sClassInfo.ssName != ptrObjectEntry->sClassInfo.ssDefaultObjectName) fnDestroyDependingObjects(GetDependingObjectInstancesByClass(ptrObjectEntry->sClassInfo.ssDefaultObjectName)); + // Dynamic dependance from object entry + std::vector vecDependingObjects; + for (const std::string& rssDependingObject : ptrObjectEntry->setDependentObjects) + { + auto itObject = m_mapServiceObjects.find(rssDependingObject); + if (itObject != m_mapServiceObjects.end()) + vecDependingObjects.push_back((*itObject->second)->tObjectID); + } + fnDestroyDependingObjects(vecDependingObjects); + // Shutdown the object (not all objects expose IObjectControl). auto* pObjectControl = ptrObjectEntry->ptrObject.GetInterface(); if (pObjectControl && pObjectControl->GetObjectState() != sdv::EObjectState::destruction_pending) @@ -735,7 +913,8 @@ bool CRepository::DestroyObject2(/*in*/ const sdv::u8string& ssObjectName) sdv::IInterfaceAccess* pObject = ptrObjectEntry->ptrObject; if (ptrObjectEntry->ptrIsoMon) pObject = ptrObjectEntry->ptrIsoMon->GetContainedObject(); - ptrObjectEntry->ptrModule->DestroyObject(pObject); + rlstDeferredObjectDestruction.push_back(std::make_pair(ptrObjectEntry, pObject)); + //ptrObjectEntry->ptrModule->DestroyObject(pObject); } return true; @@ -805,15 +984,22 @@ sdv::core::TObjectID CRepository::RegisterObject(IInterfaceAccess* pObjectIfc, c return tObjectID; } -void CRepository::LinkCoreRepository(/*in*/ sdv::IInterfaceAccess* pCoreRepository) +sdv::core::TLinkID CRepository::LinkCoreRepository(/*in*/ sdv::IInterfaceAccess* pCoreRepository) { - if (!pCoreRepository) return; - m_ptrCoreRepoAccess = pCoreRepository; + if (!pCoreRepository) return 0; + std::unique_lock lock(m_mtxObjects); + sdv::core::TLinkID tLinkID = CreateObjectID(); + m_lstCoreRepoAccess.push_back(std::make_pair(tLinkID, pCoreRepository)); + return tLinkID; } -void CRepository::UnlinkCoreRepository() +void CRepository::UnlinkCoreRepository(sdv::core::TLinkID tLinkID) { - m_ptrCoreRepoAccess = nullptr; + std::unique_lock lock(m_mtxObjects); + auto itLink = std::find_if( + m_lstCoreRepoAccess.begin(), m_lstCoreRepoAccess.end(), [&](const auto rprLink) + { return rprLink.first == tLinkID; }); + m_lstCoreRepoAccess.erase(itLink); } sdv::SClassInfo CRepository::FindClass(/*in*/ const sdv::u8string& ssClassName) const @@ -824,16 +1010,34 @@ sdv::SClassInfo CRepository::FindClass(/*in*/ const sdv::u8string& ssClassName) if (GetAppSettings().IsMainApplication() || GetAppSettings().IsIsolatedApplication()) { auto optManifest = GetAppConfig().FindInstalledComponent(ssClassName); - if (!optManifest) return {}; - return *optManifest; + if (optManifest) + return *optManifest; + } + else + { + // Get the information through the module. + auto ptrModule = GetModuleControl().FindModuleByClass(ssClassName); + if (ptrModule) + { + auto optClassInfo = ptrModule->GetClassInfo(ssClassName); + if (optClassInfo) return *optClassInfo; + } } - // Get the information through the module. - auto ptrModule = GetModuleControl().FindModuleByClass(ssClassName); - if (!ptrModule) return {}; - auto optClassInfo = ptrModule->GetClassInfo(ssClassName); - if (!optClassInfo) return {}; - return *optClassInfo; + // Copy the linked repository and then unlock; could cause a deadlock instead. + auto lstCoreRepoAccessCopy = m_lstCoreRepoAccess; + lock.unlock(); + + // If there is a linked core, use the core to get class list + for (auto& rprLink : lstCoreRepoAccessCopy) + { + const sdv::core::IRepositoryInfo* pCoreRepoInfo = rprLink.second.GetInterface(); + sdv::SClassInfo sInfo = pCoreRepoInfo->FindClass(ssClassName); + if (!sInfo.ssName.empty()) return sInfo; + } + + // Nothing found + return {}; } sdv::sequence CRepository::GetObjectList() const @@ -841,13 +1045,6 @@ sdv::sequence CRepository::GetObjectList() const std::shared_lock lock(m_mtxObjects); sdv::sequence seqObjects; - // If there is a linked core, use the core to get object list - if (m_ptrCoreRepoAccess) - { - const sdv::core::IRepositoryInfo* pCoreRepoInfo = m_ptrCoreRepoAccess.GetInterface(); - return pCoreRepoInfo ? pCoreRepoInfo->GetObjectList() : seqObjects; - } - // Add object function auto fnAddObject = [&seqObjects](const std::shared_ptr& rptrObjectEntry) { @@ -878,6 +1075,21 @@ sdv::sequence CRepository::GetObjectList() const for (const auto& prLocalObject : m_mapLocalObjects) fnAddObject(prLocalObject.second); + // Copy the linked repository and then unlock; could cause a deadlock instead. + auto lstCoreRepoAccessCopy = m_lstCoreRepoAccess; + lock.unlock(); + + // If there is a linked core, use the core to get object list + for (auto& rprLink : lstCoreRepoAccessCopy) + { + const sdv::core::IRepositoryInfo* pCoreRepoInfo = rprLink.second.GetInterface(); + if (pCoreRepoInfo) + { + auto seqListRemote = pCoreRepoInfo->GetObjectList(); + seqObjects.insert(seqObjects.end(), seqListRemote.begin(), seqListRemote.end()); + } + } + return seqObjects; } @@ -885,54 +1097,78 @@ sdv::core::SObjectInfo CRepository::GetObjectInfo(/*in*/ sdv::core::TObjectID tO { std::shared_lock lock(m_mtxObjects); - // If there is a linked core, use the core to get object info - if (m_ptrCoreRepoAccess) + // Use the internal object map for this. + sdv::core::SObjectInfo sInfo{}; + auto itObject = m_mapObjects.find(tObjectID); + if (itObject != m_mapObjects.end()) { - const sdv::core::IRepositoryInfo* pCoreRepoInfo = m_ptrCoreRepoAccess.GetInterface(); - return pCoreRepoInfo ? pCoreRepoInfo->GetObjectInfo(tObjectID) : sdv::core::SObjectInfo{}; + if (!itObject->second) return {}; + + // Fill in information + sInfo.tObjectID = itObject->second->tObjectID; + sInfo.tModuleID = itObject->second->ptrModule ? itObject->second->ptrModule->GetModuleID() : 0; + sInfo.sClassInfo = itObject->second->sClassInfo; + sInfo.ssObjectName = itObject->second->ssName; + sInfo.ssObjectConfig = itObject->second->ssConfig; + sInfo.uiFlags = 0; + if (itObject->second->bControlled) + sInfo.uiFlags |= static_cast(sdv::core::EObjectInfoFlags::object_controlled); + if (!itObject->second->ptrModule) + sInfo.uiFlags |= static_cast(sdv::core::EObjectInfoFlags::object_foreign); + if (!itObject->second->bIsolated) + sInfo.uiFlags |= static_cast(sdv::core::EObjectInfoFlags::object_isolated); + + return sInfo; } - // Use the internal object map for this. - auto itObject = m_mapObjects.find(tObjectID); - if (itObject == m_mapObjects.end()) return {}; - if (!itObject->second) return {}; + // Copy the linked repository and then unlock; could cause a deadlock instead. + auto lstCoreRepoAccessCopy = m_lstCoreRepoAccess; + lock.unlock(); - // Fill in information - sdv::core::SObjectInfo sInfo{}; - sInfo.tObjectID = itObject->second->tObjectID; - sInfo.tModuleID = itObject->second->ptrModule ? itObject->second->ptrModule->GetModuleID() : 0; - sInfo.sClassInfo = itObject->second->sClassInfo; - sInfo.ssObjectName = itObject->second->ssName; - sInfo.ssObjectConfig = itObject->second->ssConfig; - sInfo.uiFlags = 0; - if (itObject->second->bControlled) - sInfo.uiFlags |= static_cast(sdv::core::EObjectInfoFlags::object_controlled); - if (!itObject->second->ptrModule) - sInfo.uiFlags |= static_cast(sdv::core::EObjectInfoFlags::object_foreign); - if (!itObject->second->bIsolated) - sInfo.uiFlags |= static_cast(sdv::core::EObjectInfoFlags::object_isolated); + // If there is a linked core, use the core to get object info + for (auto& rprLink : lstCoreRepoAccessCopy) + { + const sdv::core::IRepositoryInfo* pCoreRepoInfo = rprLink.second.GetInterface(); + if (pCoreRepoInfo) + { + sInfo = pCoreRepoInfo->GetObjectInfo(tObjectID); + if (sInfo.tObjectID == tObjectID) return sInfo; + } + } - return sInfo; + // Info not found + return {}; } sdv::core::SObjectInfo CRepository::FindObject(/*in*/ const sdv::u8string& ssObjectName) const { std::shared_lock lock(m_mtxObjects); - // If there is a linked core, use the core to get object info - if (m_ptrCoreRepoAccess) - { - const sdv::core::IRepositoryInfo* pCoreRepoInfo = m_ptrCoreRepoAccess.GetInterface(); - return pCoreRepoInfo ? pCoreRepoInfo->FindObject(ssObjectName) : sdv::core::SObjectInfo{}; - } - // Use internal service map for this. auto itService = m_mapServiceObjects.find(ssObjectName); - if (itService == m_mapServiceObjects.end()) return {}; - auto ptrObjectEntry = *itService->second; - if (!ptrObjectEntry) return {}; + if (itService != m_mapServiceObjects.end()) + { + auto ptrObjectEntry = *itService->second; + if (!ptrObjectEntry) return {}; + return GetObjectInfo(ptrObjectEntry->tObjectID); + } + + // Copy the linked repository and then unlock; could cause a deadlock instead. + auto lstCoreRepoAccessCopy = m_lstCoreRepoAccess; lock.unlock(); - return GetObjectInfo(ptrObjectEntry->tObjectID); + + // If there is a linked core, use the core to get object info + for (auto& rprLink : lstCoreRepoAccessCopy) + { + const sdv::core::IRepositoryInfo* pCoreRepoInfo = rprLink.second.GetInterface(); + if (pCoreRepoInfo) + { + sdv::core::SObjectInfo sInfo = pCoreRepoInfo->FindObject(ssObjectName); + if (sInfo.tObjectID) return sInfo; + } + } + + return {}; } void CRepository::OnDestroyObject(sdv::IInterfaceAccess* pObject) @@ -998,9 +1234,10 @@ void CRepository::DestroyAllObjects(const std::vector& rvecIgnoreOb lock.unlock(); // Destroy the objects in reverse order + TDeferredObjectDestructionList lstDeferredObjectDestruction; while (lstCopy.size()) { - std::string ssObjectName = lstCopy.back() ? lstCopy.back()->ssName : std::string{}; + sdv::u8string ssObjectName = lstCopy.back() ? lstCopy.back()->ssName : sdv::u8string{}; lstCopy.pop_back(); if (ssObjectName.empty()) continue; @@ -1009,7 +1246,7 @@ void CRepository::DestroyAllObjects(const std::vector& rvecIgnoreOb continue; // Destroy the object... -> call the repository function to remove it from the list. - DestroyObject(ssObjectName); + DestroyObject2(ssObjectName, true, lstDeferredObjectDestruction); // Needs force! if (bForce) @@ -1038,6 +1275,34 @@ void CRepository::DestroyAllObjects(const std::vector& rvecIgnoreOb lock.unlock(); } } + for (auto& rprObject : lstDeferredObjectDestruction) + rprObject.first->ptrModule->DestroyObject(rprObject.second); +} + +void CRepository::AddObjectDependency(/*in*/ const sdv::u8string& ssObjectName, /*in*/ const sdv::u8string& ssDependsOnObject) +{ + std::unique_lock lock(m_mtxObjects); + auto itService = m_mapServiceObjects.find(ssDependsOnObject); + if (itService != m_mapServiceObjects.end()) + { + auto ptrObjectEntry = *itService->second; + if (!ptrObjectEntry) return; + auto itDependsOn = ptrObjectEntry->setDependentObjects.find(ssObjectName); + if (itDependsOn == ptrObjectEntry->setDependentObjects.end()) + ptrObjectEntry->setDependentObjects.insert(ssObjectName); + } +} + +void CRepository::RemoveObjectDependency(/*in*/ const sdv::u8string& ssObjectName, /*in*/ const sdv::u8string& ssDependsOnObject) +{ + std::unique_lock lock(m_mtxObjects); + auto itService = m_mapServiceObjects.find(ssDependsOnObject); + if (itService != m_mapServiceObjects.end()) + { + auto ptrObjectEntry = *itService->second; + if (!ptrObjectEntry) return; + ptrObjectEntry->setDependentObjects.erase(ssObjectName); + } } void CRepository::ResetConfigBaseline() @@ -1100,8 +1365,9 @@ sdv::core::EConfigProcessResult CRepository::StartFromConfig(const CAppConfigFil // Destroy the objects when an error occurred during loading. if (!bAllowPartialLoad && nFail) { + TDeferredObjectDestructionList lstDeferredObjectDestruction; for (const std::string& rssName : vecLoadedObjects) - DestroyObject(rssName); + DestroyObject2(rssName, true, lstDeferredObjectDestruction); return sdv::core::EConfigProcessResult::failed; } @@ -1185,11 +1451,14 @@ sdv::core::TObjectID CRepository::CreateIsolatedObject(const sdv::SClassInfo& rs sdv::TInterfaceAccessPtr(GetObject("CommunicationControl")).GetInterface(); if (!pConnectionControl) return 0; sdv::u8string ssConnectionString; + std::optional optPermissionObject = + GetPermissionControl().CreatePermissionObject(sdv::core::EAccessPermission::local_access); sdv::com::TConnectionID tConnection = pConnectionControl->CreateServerConnection(sdv::com::EChannelType::local_channel, sdv::core::GetObject("RepositoryService"), 5000, ssConnectionString); if (!tConnection.uiControl) return 0; if (ssConnectionString.empty()) return 0; + optPermissionObject.reset(); // Create the isolation process configuration std::stringstream sstreamConfig; @@ -1298,7 +1567,10 @@ sdv::core::TObjectID CRepository::InternalCreateObject(const std::shared_ptrptrModule->DestroyObject(rprObject.second); return 0; } @@ -1306,7 +1578,7 @@ sdv::core::TObjectID CRepository::InternalCreateObject(const std::shared_ptr(); if (pObjectControl) { - pObjectControl->Initialize(rssObjectConfig); + pObjectControl->Initialize(*ptrObjectEntry); if (pObjectControl->GetObjectState() != sdv::EObjectState::initialized) { // Shutdown the object (even if the initialization didn't work properly). @@ -1315,8 +1587,8 @@ sdv::core::TObjectID CRepository::InternalCreateObject(const std::shared_ptrDestroyObject(ptrObject); - // Destroy the object again - DestroyObject(rssObjectName); + //// Destroy the object again + //DestroyObject2(rssObjectName, true); return 0; } switch (GetAppControl().GetOperationState()) @@ -1350,14 +1622,7 @@ sdv::core::TObjectID CRepository::InternalCreateObject(const std::shared_ptr(time(0))); - tCurrent = 0; - while (!tCurrent) tCurrent = std::rand(); - } - return ++tCurrent; + return m_idgen.Generate(); } std::vector CRepository::GetDependingObjectInstancesByClass(const std::string& rssClass) @@ -1378,6 +1643,7 @@ std::vector CRepository::GetDependingObjectInstancesByClas bool CRepositoryService::EnableRepositoryObjectControl() { return GetAppSettings().IsMainApplication() || + GetAppSettings().IsMaintenanceApplication() || GetAppSettings().IsStandaloneApplication() || GetAppSettings().IsEssentialApplication() || GetAppSettings().IsIsolatedApplication(); diff --git a/sdv_services/core/repository.h b/sdv_services/core/repository.h index 448e099..2bd9d82 100644 --- a/sdv_services/core/repository.h +++ b/sdv_services/core/repository.h @@ -27,6 +27,7 @@ #include "object_lifetime_control.h" #include "iso_monitor.h" #include "app_config_file.h" +#include "../../global/unique_id.h" /** * @brief repository service providing functionality to load modules, create objects and access exiting objects @@ -34,7 +35,8 @@ class CRepository : public sdv::IInterfaceAccess, public sdv::core::IObjectAccess, public sdv::core::IRepositoryUtilityCreate, public sdv::core::IRepositoryMarshallCreate, public sdv::core::IRepositoryControl, public sdv::core::IRegisterForeignObject, - public sdv::core::IRepositoryInfo, public IObjectDestroyHandler, public sdv::core::ILinkCoreRepository + public sdv::core::IRepositoryInfo, public IObjectDestroyHandler, public sdv::core::ILinkCoreRepository, + public sdv::core::IObjectDependency { public: /** @@ -45,6 +47,7 @@ public: // Interface map BEGIN_SDV_INTERFACE_MAP() SDV_INTERFACE_ENTRY(sdv::core::IObjectAccess) + SDV_INTERFACE_ENTRY(sdv::core::IObjectDependency) SDV_INTERFACE_ENTRY(sdv::core::IRepositoryMarshallCreate) SDV_INTERFACE_ENTRY(sdv::core::IRepositoryUtilityCreate) END_SDV_INTERFACE_MAP() @@ -102,7 +105,6 @@ public: */ virtual sdv::IInterfaceAccess* CreateStubObject(/*in*/ sdv::interface_id id) override; -protected: /** * @brief Create an object and all its objects it depends on. Overload of Overload of * sdv::core::IRepositoryControl::CreateObject. @@ -127,7 +129,7 @@ protected: */ virtual sdv::core::TObjectID CreateObject(/*in*/ const sdv::u8string& ssClassName, /*in*/ const sdv::u8string& ssObjectName, /*in*/ const sdv::u8string& ssObjectConfig) override; -public: + /** * @brief Creates an object from a previously loaded module. Provide the module ID to explicitly define what module to * use during object creation. Overload of sdv::core::IRepositoryControl::CreateObjectFromModule. @@ -145,7 +147,6 @@ public: /*in*/ const sdv::u8string& ssClassName, /*in*/ const sdv::u8string& ssObjectName, /*in*/ const sdv::u8string& ssObjectConfig) override; -protected: /** * @brief Destroy a previously created object with the supplied name. Overload of sdv::core::IRepositoryControl::DestroyObject. * @details For standalone and essential applications previously created system, device and service objects can be @@ -156,7 +157,29 @@ protected: */ virtual bool DestroyObject(/*in*/ const sdv::u8string& ssObjectName) override; -public: +private: + /** + * @brief Object entry + * @details The object instance information. Objects that are running remotely are represented by their proxy. Objects can have + * multiple references by stubs. + */ + struct SObjectEntry : sdv::SObjectInfo + { + sdv::TInterfaceAccessPtr ptrObject; ///< Object interface (could be proxy). + std::shared_ptr ptrModule; ///< Module instance. + bool bControlled = false; ///< When set, the object is controlled. + bool bIsolated = false; ///< When set, the object is isolated and running in another process. + std::mutex mtxConnect; ///< Mutex used to wait for connection + std::condition_variable cvConnect; ///< Condition variable used to wait for connection + std::shared_ptr ptrIsoMon; ///< Object being monitored for shutdown. + std::set setDependentObjects; ///< List of dependent objects (needed during shutdown). + }; + + /** + * @brief List for collected objects ready for destruction. + */ + using TDeferredObjectDestructionList = std::list, sdv::IInterfaceAccess*>>; + /** * @brief Create an object and all its objects it depends on. Internal function not accessible through the interface. * @param[in] ssClassName The name of the object class to be created. For the main application, the class string could @@ -166,12 +189,13 @@ public: * the class name. Use the returned object ID to request the name of the object. * @param[in] ssObjectConfig Optional configuration handed over to the object upon creation via IObjectControl. Only * valid for standalone, essential and isolated applications. + * @param[in] bIndirect Set when the function was called through a call from GetObject. * @return Returns the object ID when the object creation was successful or 0 when not. On success the object is * available through the IObjectAccess interface. If the object already exists (class and object names are identical), * the object ID of the existing object is returned. */ - sdv::core::TObjectID CreateObject2(/*in*/ const sdv::u8string& ssClassName, /*in*/ const sdv::u8string& ssObjectName, - /*in*/ const sdv::u8string& ssObjectConfig); + sdv::core::TObjectID CreateObject2(const sdv::u8string& ssClassName, const sdv::u8string& ssObjectName, + const sdv::u8string& ssObjectConfig, bool bIndirect); /** * @brief Destroy a previously created object with the supplied name. Internal function not accessible through the interface. @@ -179,10 +203,14 @@ public: * destroyed. For the main and isolated applications, only the complex service can be destroyed. For isolated * applications a destruction of the object will end the application. * @param[in] ssObjectName The name of the object to destroy. + * @param[in] bIndirect Set when the function was not call directly through DestroyObject. + * @param[in] rlstDeferredObjectDestruction Reference to the list of objects to be filled with objects to be destroyed. * @return Returns whether the object destruction was successful. */ - bool DestroyObject2(/*in*/ const sdv::u8string& ssObjectName); + bool DestroyObject2(const sdv::u8string& ssObjectName, bool bIndirect, + TDeferredObjectDestructionList& rlstDeferredObjectDestruction); +public: /** * @brief Register as foreign object and make it public to the system with the given name. Overload of * sdv::core::IRegisterForeignObject::RegisterObject. @@ -196,15 +224,17 @@ public: /*in*/ const sdv::u8string& ssObjectName) override; /** - * @brief Register the core repository. + * @brief Register the core repository. Overload of sdv::core::ILinkCoreRepository::LinkCoreRepository. * @param[in] pCoreRepository Pointer to the proxy interface of the core repository. + * @return Returns a link ID to be used in the Unlink function. Or 0 on failure. */ - virtual void LinkCoreRepository(/*in*/ sdv::IInterfaceAccess* pCoreRepository) override; + virtual sdv::core::TLinkID LinkCoreRepository(/*in*/ sdv::IInterfaceAccess* pCoreRepository) override; /** - * @brief Unlink a previously linked core repository. + * @brief Unlink a previously linked core repository. Overload of sdv::core::ILinkCoreRepository::UnlinkCoreRepository. + * @param[in] tLinkID Link ID of the repository link to remove. */ - virtual void UnlinkCoreRepository() override; + virtual void UnlinkCoreRepository(sdv::core::TLinkID tLinkID) override; /** * @brief Find the class information of an object with the supplied name. Overload of @@ -256,6 +286,20 @@ public: */ void DestroyAllObjects(const std::vector&rvecIgnoreObjects, bool bForce = false); + /** + * @brief Add a dependency for an object. Overload of sdv::core::IObjectDependency::AddObjectDependency. + * @param[in] ssObjectName Name of the object. + * @param[in] ssDependsOnObject Name of the object it depends on. + */ + void AddObjectDependency(/*in*/ const sdv::u8string& ssObjectName, /*in*/ const sdv::u8string& ssDependsOnObject) override; + + /** + * @brief Remove an object dependency. Overload of sdv::core::IObjectDependency::RemoveObjectDependency. + * @param[in] ssObjectName Name of the object. + * @param[in] ssDependsOnObject Name of the object it depends on. + */ + void RemoveObjectDependency(/*in*/ const sdv::u8string& ssObjectName, /*in*/ const sdv::u8string& ssDependsOnObject) override; + /** * @brief Reset the current config baseline. */ @@ -306,7 +350,7 @@ private: * @brief Create a new unique object ID. * @return The created object ID. */ - static sdv::core::TObjectID CreateObjectID(); + sdv::core::TObjectID CreateObjectID(); /** * @brief Get a list of depending object instances of a specific class. @@ -315,26 +359,6 @@ private: */ std::vector GetDependingObjectInstancesByClass(const std::string& rssClass); - /** - * @brief Object entry - * @details The object instance information. Objects that are running remotely are represented by their proxy. Objects can have - * multiple references by stubs. - */ - struct SObjectEntry - { - sdv::core::TObjectID tObjectID = 0; ///< Object ID (local to this process). - sdv::SClassInfo sClassInfo; ///< Object class name. - std::string ssName; ///< Object name (can be zero with local objects). - std::string ssConfig; ///< Object configuration. - sdv::TInterfaceAccessPtr ptrObject; ///< Object interface (could be proxy). - std::shared_ptr ptrModule; ///< Module instance. - bool bControlled = false; ///< When set, the object is controlled. - bool bIsolated = false; ///< When set, the object is isolated and running in another process. - std::mutex mtxConnect; ///< Mutex used to wait for connection - std::condition_variable cvConnect; ///< Condition variable used to wait for connection - std::shared_ptr ptrIsoMon; ///< Object being monitored for shutdown. - }; - using TObjectMap = std::map>; using TOrderedObjectList = std::list>; using TObjectIDList = std::list>; @@ -350,11 +374,11 @@ private: TIsolationMap m_mapIsolatedObjects; ///< Map with isolated objects. TObjectMap m_mapObjects; ///< Map with all objects indexed by the object ID. TConfigSet m_setConfigObjects; ///< Set with the objects for storing in the configuration. - sdv::TInterfaceAccessPtr m_ptrCoreRepoAccess; ///< Linked core repository access (proxy interface). + std::list> m_lstCoreRepoAccess; ///< List to Linked core repositories. bool m_bIsoObjectLoaded = false; ///< When set, the isolated object has loaded. Do not allow ///< another object of type complex service or utility to be ///< created. - /// + CUniqueID m_idgen; ///< Unique ID generator. }; /** diff --git a/sdv_services/core/sdv_core.h b/sdv_services/core/sdv_core.h index eee40ef..a613a83 100644 --- a/sdv_services/core/sdv_core.h +++ b/sdv_services/core/sdv_core.h @@ -23,6 +23,7 @@ #include "logger_control.h" #include "logger.h" #include "app_config.h" +#include "permission_control.h" /** * @brief SDV core instance class containing containing the instances for the core services. @@ -47,6 +48,7 @@ public: SDV_INTERFACE_CHAIN_MEMBER(GetMemoryManager()) SDV_INTERFACE_CHAIN_MEMBER(GetRepository()) SDV_INTERFACE_CHAIN_MEMBER(GetLoggerControl()) + SDV_INTERFACE_CHAIN_MEMBER(GetPermissionControl()) END_SDV_INTERFACE_MAP() /** diff --git a/sdv_services/core/toml_parser/code_snippet.cpp b/sdv_services/core/toml_parser/code_snippet.cpp index 9bf1654..1fbfb01 100644 --- a/sdv_services/core/toml_parser/code_snippet.cpp +++ b/sdv_services/core/toml_parser/code_snippet.cpp @@ -37,7 +37,8 @@ namespace toml_parser TTokenListIterator it = m_lstTokens.begin(); bool bCommaAvailable = false; bool bPrintableCharsAvailable = false; - bool bLastTokenIsComment = false; + bool bLastTokenIsCommentOrForceNewline = false; + bool bNewlineAllowed = rContext.NewlineAllowed() || (eMode == EComposeMode::compose_behind && rContext.FinalNewline()); while (it != m_lstTokens.end() && it->Category() != ETokenCategory::token_comment) { // Add comma only when needed. @@ -53,12 +54,12 @@ namespace toml_parser bCommaAvailable = true; break; case ETokenCategory::token_comment: - bSkip = !rContext.CommentAndNewlineAllowed(); - if (!bSkip) bLastTokenIsComment = true; + bSkip = !bNewlineAllowed; + if (!bSkip) bLastTokenIsCommentOrForceNewline = true; break; case ETokenCategory::token_syntax_new_line: - bSkip = !rContext.NewlineAllowed(); - if (!bSkip) bLastTokenIsComment = false; + bSkip = !bNewlineAllowed; + if (!bSkip) bLastTokenIsCommentOrForceNewline = false; break; default: break; @@ -107,11 +108,11 @@ namespace toml_parser break; case ETokenCategory::token_comment: bSkip = !rContext.CommentAndNewlineAllowed(); - if (!bSkip) bLastTokenIsComment = true; + if (!bSkip) bLastTokenIsCommentOrForceNewline = true; break; case ETokenCategory::token_syntax_new_line: - bSkip = !rContext.NewlineAllowed(); - if (!bSkip) bLastTokenIsComment = false; + bSkip = !bNewlineAllowed; + if (!bSkip) bLastTokenIsCommentOrForceNewline = false; break; default: break; @@ -210,11 +211,11 @@ namespace toml_parser break; case ETokenCategory::token_comment: bSkip = !rContext.CommentAndNewlineAllowed(); - if (!bSkip) bLastTokenIsComment = true; + if (!bSkip) bLastTokenIsCommentOrForceNewline = true; break; case ETokenCategory::token_syntax_new_line: - bSkip = !rContext.NewlineAllowed(); - if (!bSkip) bLastTokenIsComment = false; + bSkip = !bNewlineAllowed; + if (!bSkip) bLastTokenIsCommentOrForceNewline = false; break; default: break; @@ -239,7 +240,7 @@ namespace toml_parser } // Default newline needed - if (rContext.FinalNewline() && ((m_lstTokens.empty() && m_ssComment.empty()) || bLastTokenIsComment)) + if (rContext.FinalNewline() && ((m_lstTokens.empty() && m_ssComment.empty()) || bLastTokenIsCommentOrForceNewline)) sstream << std::endl; break; case EComposeMode::compose_before: diff --git a/sdv_services/core/toml_parser/miscellaneous.cpp b/sdv_services/core/toml_parser/miscellaneous.cpp index 02208ac..a06ef5c 100644 --- a/sdv_services/core/toml_parser/miscellaneous.cpp +++ b/sdv_services/core/toml_parser/miscellaneous.cpp @@ -16,6 +16,8 @@ #include "exception.h" #include #include +#include +#include "parser_toml.h" namespace toml_parser { @@ -560,4 +562,21 @@ namespace toml_parser return sstreamQuotedText.str(); } } + + bool CompareEqual(const std::string& rssToml1, const std::string& rssToml2) + { + try + { + CParser parser1(rssToml1); + CParser parser2(rssToml2); + sdv::toml::CNodeCollection collection1(&parser1.Root()); + sdv::toml::CNodeCollection collection2(&parser2.Root()); + return sdv::toml::internal::CompareNodes(collection1, collection2) == sdv::toml::ECompareResult::compare_identical; + } + catch (const toml_parser::XTOMLParseException&) + { + return false; + } + } + } // namespace toml_parser \ No newline at end of file diff --git a/sdv_services/core/toml_parser/miscellaneous.h b/sdv_services/core/toml_parser/miscellaneous.h index cfa2885..b3a6402 100644 --- a/sdv_services/core/toml_parser/miscellaneous.h +++ b/sdv_services/core/toml_parser/miscellaneous.h @@ -108,6 +108,16 @@ namespace toml_parser * @return The quoted key (if applicable); otherwise the key without quotes. */ std::string QuoteText(const std::string& rssText, EQuoteRequest eQuoteRequest = EQuoteRequest::smart_text); + + /** + * @brief Compare the content of TOML string with the content of another TOML string. Use internal TOML parser for the + * comparison. + * @param[in] rssToml1 Reference to the first TOML string to compare with the second TOML string. + * @param[in] rssToml2 Reference to the second TOML string to compare with the first TOML string. + * @return The comparison result. + */ + bool CompareEqual(const std::string& rssToml1, const std::string& rssToml2); + } // namespace toml_parser #endif // !defined MISCELLANEOUS_H \ No newline at end of file diff --git a/sdv_services/core/toml_parser/parser_node_indexer.cpp b/sdv_services/core/toml_parser/parser_node_indexer.cpp new file mode 100644 index 0000000..6276907 --- /dev/null +++ b/sdv_services/core/toml_parser/parser_node_indexer.cpp @@ -0,0 +1,145 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#include "parser_node_indexer.h" +#include +#include + +namespace toml_parser +{ + CNodeIndex::CNodeIndex(CIndexList& rIndexList, CIndexListIterator itPos) : m_ptrPos(std::make_shared(rIndexList, itPos)) + {} + + CNodeIndex::~CNodeIndex() + { + m_ptrPos.reset(); + } + + CNodeIndex::CNodeIndex(const CNodeIndex& rIndex) : m_ptrPos(rIndex.m_ptrPos) + {} + + CNodeIndex::CNodeIndex(CNodeIndex&& rIndex) : m_ptrPos(std::move(rIndex.m_ptrPos)) + {} + + CNodeIndex& CNodeIndex::operator=(const CNodeIndex& rIndex) + { + m_ptrPos = rIndex.m_ptrPos; + return *this; + } + + CNodeIndex& CNodeIndex::operator=(CNodeIndex&& rIndex) + { + m_ptrPos = std::move(rIndex.m_ptrPos); + return *this; + } + + bool CNodeIndex::operator==(const CNodeIndex& rIndex) const + { + return rIndex.m_ptrPos == m_ptrPos; + } + + bool CNodeIndex::operator!=(const CNodeIndex& rIndex) const + { + return !operator==(rIndex); + } + + bool CNodeIndex::operator<(const CNodeIndex& rIndex) const + { + if (!m_ptrPos) return false; + if (!rIndex.m_ptrPos) return true; + return m_ptrPos->Index() < rIndex.m_ptrPos->Index(); + } + + bool CNodeIndex::operator<=(const CNodeIndex& rIndex) const + { + if (!m_ptrPos) return false; + if (!rIndex.m_ptrPos) return true; + return rIndex.m_ptrPos == m_ptrPos || m_ptrPos->Index() < rIndex.m_ptrPos->Index(); + } + + bool CNodeIndex::operator>(const CNodeIndex& rIndex) const + { + if (!m_ptrPos) return true; + if (!rIndex.m_ptrPos) return false; + return m_ptrPos->Index() > rIndex.m_ptrPos->Index(); + } + + bool CNodeIndex::operator>=(const CNodeIndex& rIndex) const + { + if (!m_ptrPos) return true; + if (!rIndex.m_ptrPos) return false; + return rIndex.m_ptrPos == m_ptrPos || m_ptrPos->Index() > rIndex.m_ptrPos->Index(); + } + + CNodeIndex::operator bool() const + { + return m_ptrPos ? true : false; + } + + void CNodeIndex::MoveBefore(const CNodeIndex& rIndex) + { + if (m_ptrPos && rIndex.m_ptrPos) + m_ptrPos->MoveBeforeIndex(*rIndex.m_ptrPos); + } + + uint32_t CNodeIndex::Index() const + { + if (!m_ptrPos) return sdv::toml::npos; + return m_ptrPos->Index(); + } + + CNodeIndex::CIteratorWrapper::CIteratorWrapper(CIndexList& rIndexList, CIndexListIterator itPos) : + m_rIndexList(rIndexList), m_itPos(itPos) + {} + + CNodeIndex::CIteratorWrapper ::~CIteratorWrapper() + { + m_rIndexList.erase(m_itPos); + } + + uint32_t CNodeIndex::CIteratorWrapper::Index() const + { + return static_cast(std::distance(m_rIndexList.cbegin(), m_itPos)); + } + + void CNodeIndex::CIteratorWrapper::MoveBeforeIndex(const CIteratorWrapper& ritTarget) + { + m_rIndexList.splice(ritTarget.m_itPos, m_rIndexList, m_itPos); + } + + // Global index list + CIndexList CNodeIndexer::m_lstIndexList; + + CNodeIndexer::CNodeIndexer() + {} + + CNodeIndex CNodeIndexer::CreateIndex() + { + auto itPos = m_lstIndexList.insert(m_lstIndexList.end(), SNodeIndexElement()); + CNodeIndex index(m_lstIndexList, itPos); + return index; + } + + CNodeIndex CNodeIndexer::CreateIndex(const CNodeIndex& rInsertBefore) + { + CNodeIndex node_index = CreateIndex(); + node_index.MoveBefore(rInsertBefore); + return node_index; + } + + size_t CNodeIndexer::Count() + { + return m_lstIndexList.size(); + } + +} // namespace toml_parser \ No newline at end of file diff --git a/sdv_services/core/toml_parser/parser_node_indexer.h b/sdv_services/core/toml_parser/parser_node_indexer.h new file mode 100644 index 0000000..297fd81 --- /dev/null +++ b/sdv_services/core/toml_parser/parser_node_indexer.h @@ -0,0 +1,227 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#ifndef PARSER_NODE_INDEXER_H +#define PARSER_NODE_INDEXER_H + +#include +#include +#include + +namespace toml_parser +{ + class CNodeIndex; + class CNodeIndexer; + /// Placeholder index element + struct SNodeIndexElement {}; + /// Index list + using CIndexList = std::list; + /// Const iterator to an element in the index list + using CIndexListIterator = CIndexList::const_iterator; + + /** + * @brief Node index object used to manage the node order. + */ + class CNodeIndex + { + friend CNodeIndexer; ///< CNodeIndex uses friend access to create the node index and assign the iterator holding the + ///< the position within the index list. + private: + /** + * @brief Constructor of the node index object. Use the CNodeIndexer::CreateIndex function to create the object. + * @param[in] rIndexList Reference to the index list. + * @param[in] itPos Iterator to the position in the index list. + */ + CNodeIndex(CIndexList& rIndexList, CIndexListIterator itPos); + + public: + /** + * @brief Destructor + */ + virtual ~CNodeIndex(); + + /** + * @brief Copy constructor of the node index object. + * @param[in] rIndex Reference to the index object to copy from. + */ + CNodeIndex(const CNodeIndex& rIndex); + + /** + * @brief Move constructor of the node index object. + * @param[in] rIndex Reference to the index object to move from. + */ + CNodeIndex(CNodeIndex&& rIndex); + + /** + * @brief Copy assignment operator of the node index object. + * @param[in] rIndex Reference to the index object to copy from. + * @return Returns a reference to this object. + */ + CNodeIndex& operator=(const CNodeIndex& rIndex); + + /** + * @brief Move assignment operator of the node index object. + * @param[in] rIndex Reference to the index object to move from. + * @return Returns a reference to this object. + */ + CNodeIndex& operator=(CNodeIndex&& rIndex); + + /** + * @brief Compare this node index object to the supplied index object and return whether the position of this object is + * identical to the position of the supplied object. + * @param[in] rIndex Reference to the node index object to compare with. + * @return Returns whether the provided index is identical to this index. + */ + bool operator==(const CNodeIndex& rIndex) const; + + /** + * @brief Compare this node index object to the supplied index object and return whether the position of this object is + * not identical to the position of the supplied object. + * @param[in] rIndex Reference to the node index object to compare with. + * @return Returns whether the provided index is different than this index. + */ + bool operator!=(const CNodeIndex& rIndex) const; + + /** + * @brief Compare this node index object to the supplied index object and return whether the position of this object is + * smaller than the position of the supplied object. + * @param[in] rIndex Reference to the node index object to compare with. + * @return Returns whether the provided index is smaller than this index. + */ + bool operator<(const CNodeIndex& rIndex) const; + + /** + * @brief Compare this node index object to the supplied index object and return whether the position of this object is + * smaller than or equal to the position of the supplied object. + * @param[in] rIndex Reference to the node index object to compare with. + * @return Returns whether the provided index is smaller than or equal to this index. + */ + bool operator<=(const CNodeIndex& rIndex) const; + + /** + * @brief Compare this node index object to the supplied index object and return whether the position of this object is + * larger than the position of the supplied object. + * @param[in] rIndex Reference to the node index object to compare with. + * @return Returns whether the provided index is larger than this index. + */ + bool operator>(const CNodeIndex& rIndex) const; + + /** + * @brief Compare this node index object to the supplied index object and return whether the position of this object is + * larger than or equal to the position of the supplied object. + * @param[in] rIndex Reference to the node index object to compare with. + * @return Returns whether the provided index is larger than or equal to this index. + */ + bool operator>=(const CNodeIndex& rIndex) const; + + /** + * @brief Check for validity. + * @return Returns whether the index object contains a valid index. + */ + operator bool() const; + + /** + * @brief Move the node index object before the index object provided as an argument. + * @param[in] rIndex Reference to the node index object to move the object before. + */ + void MoveBefore(const CNodeIndex& rIndex); + + /** + * @brief Get the index in the list. Returns sdv::toml::npos if the index is not in the list any more. + * @remarks This index os calculated dynamically and can change if indices are added, removed or swapped. + * @return The current index in the list. + */ + uint32_t Index() const; + + private: + /** + * @brief Iterator wrapper object with lifetime management. + * @remarks The wrapper keeps track of the shared index list as well. It is possible that the parser is deleted before the + * nodes are deleted, causing an issue when accessing the index list. + */ + struct CIteratorWrapper + { + /** + * @brief Constructor + * @param[in] rIndexList Reference to the index list. + * @param[in] itPos Position of this index in the index list. + */ + CIteratorWrapper(CIndexList& rIndexList, CIndexListIterator itPos); + + /** + * @brief Destructor removing the index object from the index list. + */ + ~CIteratorWrapper(); + + /** + * @brief Get the index in the list. + * @remarks This index os calculated dynamically and can change if indices are added, removed or swapped. + * @return The current index in the list. + */ + uint32_t Index() const; + + /** + * @brief Move the index object before the supplied index object. + * @param[in] ritTarget Reference to the index object to move the object before. + */ + void MoveBeforeIndex(const CIteratorWrapper& ritTarget); + + private: + CIndexList& m_rIndexList; ///< Reference to the index list. + CIndexListIterator m_itPos; ///< Iterator in the list + }; + + std::shared_ptr m_ptrPos; ///< Position in the index list + }; + + /** + * @brief Node indexer object managing the node index objects allowing determining and changing the order of nodes. + */ + class CNodeIndexer + { + friend CNodeIndex; ///< CNodeIndex has friend relationship allowing access to specific indexing functions using the + ///< index iterator from the index list. + public: + + /** + * @brief Constructor + */ + CNodeIndexer(); + + /** + * @brief Create a node index object. + * @return The new node index object. + */ + CNodeIndex CreateIndex(); + + /** + * @brief Create a new node index object and insert this object before the supplied object. + * @attention There is no protection for supplying an invalid iterator or an iterator from a different indexer. + * @param[in] rInsertBefore Insert the index object before the supplied iterator position. + * @return The new node index object. + */ + CNodeIndex CreateIndex(const CNodeIndex& rInsertBefore); + + /** + * @brief Return the amount of indices allocated. + * @return The amount of indices allocated in the index list. + */ + static size_t Count(); + + private: + static CIndexList m_lstIndexList; ///< List of all node indices. This list is global to allow nodes from one + ///< parser to be inserted into the list of another parser. + }; +} // namespace toml_parser + +#endif // !defined PARSER_NODE_INDEXER_H \ No newline at end of file diff --git a/sdv_services/core/toml_parser/parser_node_toml.cpp b/sdv_services/core/toml_parser/parser_node_toml.cpp index f8be6c7..0637832 100644 --- a/sdv_services/core/toml_parser/parser_node_toml.cpp +++ b/sdv_services/core/toml_parser/parser_node_toml.cpp @@ -18,6 +18,7 @@ #include #include "parser_toml.h" #include +#include /// The TOML parser namespace namespace toml_parser @@ -41,16 +42,13 @@ namespace toml_parser } } - bool CGenContext::PartOfExcludedParents(const std::shared_ptr& rptrNode) const + bool CGenContext::IsPartOfView(const std::shared_ptr& rptrNode) const { + if (!rptrNode) return false; if (!m_ptrTopMostNode) return false; - std::shared_ptr ptrParent = m_ptrTopMostNode->GetParentPtr(); - while (ptrParent) - { - if (ptrParent == rptrNode) return true; - ptrParent = ptrParent->GetParentPtr(); - } - return false; + if (rptrNode->Inline()) return true; + if (!rptrNode->GetParentPtr()) return false; + return m_bTopMost || rptrNode->GetParentPtr() == m_ptrTopMostNode; } CGenContext CGenContext::CopyWithContext(const std::string& rssNewKeyContext, const std::shared_ptr& rptrNode, @@ -230,7 +228,7 @@ namespace toml_parser } CNode::CNode(CParser& rparser, const std::string& rssName, const std::string& rssRawName) : - m_ssName(rssName), m_ssRawName(rssRawName), m_rParser(rparser) + m_index(rparser.Indexer().CreateIndex()), m_ssName(rssName), m_ssRawName(rssRawName), m_refParser(rparser) {} CNode::~CNode() @@ -238,7 +236,32 @@ namespace toml_parser CParser& CNode::Parser() { - return m_rParser; + return m_refParser.get(); + } + + const CParser& CNode::Parser() const + { + return m_refParser.get(); + } + + const CNodeIndex& CNode::NodeIndex() const + { + return m_index; + } + + CNodeIndex& CNode::NodeIndex() + { + return m_index; + } + + bool CNode::operator<(const CNode& rNode) const + { + return m_index < rNode.m_index; + } + + bool CNode::operator<(const std::shared_ptr& rptrNode) const + { + return m_index < rptrNode->m_index; } sdv::u8string CNode::GetName() const @@ -316,11 +339,9 @@ namespace toml_parser uint32_t CNode::GetIndex() const { - std::shared_ptr ptrView = m_ptrView.lock(); - if (!ptrView) ptrView = m_ptrParent.lock(); - if (!ptrView) return sdv::toml::npos; - - return ptrView->FindIndex(std::const_pointer_cast(shared_from_this())); + auto ptrParent = GetParentPtr(); + if (!ptrParent) return sdv::toml::npos; + return ptrParent->FindIndex(std::const_pointer_cast(shared_from_this())); } sdv::IInterfaceAccess* CNode::GetParent() const @@ -347,14 +368,19 @@ namespace toml_parser return CodeSnippet(eType).GetComment(); } - void CNode::AutomaticFormat() + void CNode::AutomaticFormat(/*in*/ bool bRemoveComments) { - // Automatically format the code snippets of the node. - for (auto& rmapSnippets : m_vecCodeSnippets) + if (bRemoveComments) + m_vecCodeSnippets.clear(); + else { - for (auto& rvtSnippet : rmapSnippets) + // Automatically format the code snippets of the node. + for (auto& rmapSnippets : m_vecCodeSnippets) { - rvtSnippet.second.RemoveFormat(); + for (auto& rvtSnippet : rmapSnippets) + { + rvtSnippet.second.RemoveFormat(); + } } } } @@ -421,9 +447,8 @@ namespace toml_parser { bool bRet = false; - // Remove the node from the view (this could also be the parent). - auto ptrView = GetViewPtr(); - if (ptrView) ptrView->RemoveFromView(shared_from_this()); + // Block rebuilding + auto lock = Parser().CreateRebuildLockObject(); // Remove the node from the parent. auto ptrParent = GetParentPtr(); @@ -458,8 +483,14 @@ namespace toml_parser ptrOldParent->m_lstNodes.erase(itNode); } + // Assign the parent m_ptrParent = rptrParent; if (!rptrParent) return; + + // Assign the parser of the parent + ReassignParser(rptrParent->Parser()); + + // Add the node to the node list if not already present. auto itNode = std::find(rptrParent->m_lstNodes.begin(), rptrParent->m_lstNodes.end(), shared_from_this()); if (itNode == rptrParent->m_lstNodes.end()) rptrParent->m_lstNodes.push_back(shared_from_this()); @@ -477,26 +508,6 @@ namespace toml_parser return ptrParent->GetPath(false); } - void CNode::SetViewPtr(const std::shared_ptr& rptrView) - { - m_ptrView = rptrView; - } - - std::shared_ptr CNode::GetViewPtr() const - { - auto ptrView = m_ptrView.lock(); - if (!ptrView) ptrView = m_ptrParent.lock(); - return ptrView; - } - - bool CNode::IsPartOfView(const CGenContext& rContext, const std::shared_ptr& rptrNode) const - { - std::shared_ptr ptrStoredView = m_ptrView.lock(); - if (ptrStoredView && !rContext.PartOfExcludedParents(ptrStoredView)) - return ptrStoredView == rptrNode; - return true; - } - const CCodeSnippet& CNode::CodeSnippet(size_t nIndex, const std::string& rssKey /*= std::string()*/) const { static CCodeSnippet sEmptyCodeSnippet; @@ -635,6 +646,11 @@ namespace toml_parser return ssCustomKeyPath; } + void CNode::ReassignParser(CParser& rParser) + { + m_refParser = rParser; + } + bool CNode::ExplicitlyDefined() const { // Default implementation is explicitly. @@ -657,7 +673,7 @@ namespace toml_parser return true; } - bool CValueNode::Inline(bool bInline) + bool CValueNode::Inline(bool bInline, bool /*bIncludeChildren*/ /*= true*/) { return bInline; // When set to inline, okay; otherwise not. } @@ -919,17 +935,17 @@ namespace toml_parser CNode(rparser, rssName, rssRawName) {} - void CNodeCollection::AutomaticFormat() + void CNodeCollection::AutomaticFormat(/*in*/ bool bRemoveComments) { // Format each child-node for (std::shared_ptr& rptrNode : m_lstNodes) - rptrNode->AutomaticFormat(); - CNode::AutomaticFormat(); + rptrNode->AutomaticFormat(bRemoveComments); + CNode::AutomaticFormat(bRemoveComments); } uint32_t CNodeCollection::GetCount() const { - return static_cast(m_vecNodeOrder.size()); + return static_cast(m_lstNodes.size()); } sdv::IInterfaceAccess* CNodeCollection::GetNode(/*in*/ uint32_t uiIndex) const @@ -940,10 +956,63 @@ namespace toml_parser std::shared_ptr CNodeCollection::Get(uint32_t uiIndex) const { - if (static_cast(uiIndex) >= m_vecNodeOrder.size()) + if (static_cast(uiIndex) >= m_lstNodes.size()) return nullptr; - return m_vecNodeOrder[uiIndex]; + auto it = m_lstNodes.begin(); + std::advance(it, uiIndex); + return *it; + } + + void CNodeCollection::RebuildNodeOrder(bool bForce) + { + // Is rebuild locked? Then do not rebuild. + if (!bForce && Parser().RebuildLocked()) return; + + // Sort the nodes + m_lstNodes.sort([&](const std::shared_ptr& rptrNode1, const std::shared_ptr& rptrNode2) -> bool + { + if (!rptrNode2) return true; + if (!rptrNode1) return false; + + // Check whether only one is inline. If so, node 1 is smaller when node 1 is inline. + if (rptrNode1->Inline() != rptrNode2->Inline()) + return rptrNode1->Inline(); + + // Check for the smallest index + return *rptrNode1 < *rptrNode2; + }); + + // Call rebuild for all sub-node-collections + for (auto& rptrNode : m_lstNodes) + { + std::shared_ptr ptrCollection = rptrNode->Cast(); + if (ptrCollection) + ptrCollection->RebuildNodeOrder(bForce); + } + } + + void CNodeCollection::FillNodeOrderVector(std::vector>& rvecNodes, bool bRootView /*= true*/, + bool bTopLevel /*= true*/) + { + // Run through the nodes and add the nodes that are explicit. + for (const auto& rptrNode : m_lstNodes) + { + // If top level is enabled, count all explicit nodes. If root view is enabled, count all explicit standard tables. + bool bIsTable = rptrNode->Cast() ? true : false; + bool bIsTableArray = rptrNode->Cast() && rptrNode->Cast()->TableArray(); + bool bExplicit = rptrNode->ExplicitlyDefined(); + bool bIsInline = rptrNode->Inline(); + if (bExplicit && (bTopLevel || (bRootView && bIsTable && !bIsInline)) && !(bIsTableArray && !bIsInline)) + rvecNodes.push_back(rptrNode); + + // Add children if the node is not explicitly defined (then all children if the top level flag is set) or root view + // is set and the node is not inline, all the tables within the collection. + auto ptrCollection = rptrNode->Cast(); + std::shared_ptr ptrTargetNode; + if (ptrCollection && ((bRootView && !ptrCollection->Inline()) || !rptrNode->ExplicitlyDefined())) + ptrCollection->FillNodeOrderVector(rvecNodes, bRootView, bTopLevel && !rptrNode->ExplicitlyDefined()); + } } std::shared_ptr CNodeCollection::Direct(const std::string& rssPath) const @@ -974,14 +1043,92 @@ namespace toml_parser return static_cast(ptrNode.get()); } - sdv::IInterfaceAccess* CNodeCollection::InsertValue(uint32_t uiIndex, const sdv::u8string& ssName, sdv::any_t anyValue) + std::pair, std::string> CNodeCollection::SmartParentCreate(const std::string& rssPath, + bool bInsertTableArray /*= false*/) { + auto prKey = SplitNodeKey(rssPath); + if (prKey.first.empty()) return std::make_pair(Cast(), rssPath); + if (prKey.second.empty()) + { + // Check to see if the key exists already. If so, this could be an array. If not, return. + // Special treatment for table arrays needed. + auto ptrNode = Direct(prKey.first); + if (ptrNode && ptrNode->GetType() == sdv::toml::ENodeType::node_array && + (!bInsertTableArray || !ptrNode->Cast()->TableArray())) + return std::make_pair(ptrNode->Cast(), ""); + else + return std::make_pair(Cast(), rssPath); + } + + // Determine whether the node should be a table or an array. For this take the next name part and check for an index. + auto prKeyNext = SplitNodeKey(prKey.second); + sdv::toml::ENodeType eTargetType = + (!prKeyNext.first.empty() && prKeyNext.first.find_first_not_of("0123456789") != std::string::npos) ? + sdv::toml::ENodeType::node_table : sdv::toml::ENodeType::node_array; + + // Find the node + std::shared_ptr ptrNodeCollection; + for (auto& rptrNode : m_lstNodes) + { + if (!rptrNode) continue; + if (rptrNode->GetName() == prKey.first) + { + ptrNodeCollection = rptrNode->Cast(); + if (rptrNode->GetType() != eTargetType) + { + // Special case... the type is an array, but no index was supplied. Use the index of the last element or if none + // exists, the largest index number to indicate a new element. + if (rptrNode->GetType() == sdv::toml::ENodeType::node_array) + return SmartParentCreate( + prKey.first + "[" + std::to_string(ptrNodeCollection->GetCount() - 1) + "]." + prKey.second, + bInsertTableArray); + + // Incorrect type + return std::make_pair(nullptr, rssPath); + } + break; + } + } + + // If not found, create the node collection + if (!ptrNodeCollection) + { + // Determine whether the next key part is a number - then create an array or whether the next key part is a name, then + // create a table. + if (eTargetType == sdv::toml::ENodeType::node_array) + { + auto* pArray = dynamic_cast(InsertArray("", prKey.first)); + if (pArray) ptrNodeCollection = pArray->Cast(); + } + else + { + auto* pTable = dynamic_cast(InsertTable("", prKey.first, + sdv::toml::EInsertPreference::prefer_standard)); + if (pTable) ptrNodeCollection = pTable->Cast(); + } + if (!ptrNodeCollection) return std::make_pair(nullptr, rssPath); // Not found + } + + // Get or create next node + return ptrNodeCollection->SmartParentCreate(prKey.second, bInsertTableArray); + } + + sdv::IInterfaceAccess* CNodeCollection::InsertValue(/*in*/ const sdv::u8string& ssInsertBefore, + /*in*/ const sdv::u8string& ssName, /*in*/ sdv::any_t anyValue) + { + // Get the parent to inser this node into + auto prParent = SmartParentCreate(ssName); + if (!prParent.first) return nullptr; + + // Determine the node to insert before and make sure that the node is not occurring before the parent. + std::shared_ptr ptrInsertBefore = Direct(ssInsertBefore); + if (ptrInsertBefore && (ptrInsertBefore == prParent.first || ptrInsertBefore < prParent.first)) + ptrInsertBefore = prParent.first->Get(0); + + // Create TOML text std::stringstream sstreamTOML; - std::string ssAccessName = ssName; - if (GetType() == sdv::toml::ENodeType::node_array) - ssAccessName = "[" + std::to_string(std::min(uiIndex, GetCount())) + "]"; - else - sstreamTOML << QuoteText(ssName, EQuoteRequest::smart_key) << " = "; + if (prParent.first->GetType() != sdv::toml::ENodeType::node_array) + sstreamTOML << QuoteText(prParent.second, EQuoteRequest::smart_key) << " = "; switch (anyValue.eValType) { case sdv::any_t::EValType::val_type_bool: @@ -1023,114 +1170,139 @@ namespace toml_parser } // Insert the node - if (InsertTOML(uiIndex, sstreamTOML.str(), true) != EInsertResult::insert_success) + auto prResult = prParent.first->InsertTOML(ptrInsertBefore, sstreamTOML.str(), true); + if (prResult.first != EInsertResult::insert_success || prResult.second.empty()) return nullptr; - + // Return the inserted node. - return GetNodeDirect(ssAccessName); + return prResult.second.back().get(); } - sdv::IInterfaceAccess* CNodeCollection::InsertArray(uint32_t uiIndex, const sdv::u8string& ssName) + sdv::IInterfaceAccess* CNodeCollection::InsertArray(/*in*/ const sdv::u8string& ssInsertBefore, + /*in*/ const sdv::u8string& ssName) { + // Get the parent to inser this node into + auto prParent = SmartParentCreate(ssName); + if (!prParent.first) return nullptr; + + // Determine the node to insert before and make sure that the node is not occurring before the parent. + std::shared_ptr ptrInsertBefore = Direct(ssInsertBefore); + if (ptrInsertBefore && (ptrInsertBefore == prParent.first || ptrInsertBefore < prParent.first)) + ptrInsertBefore = prParent.first->Get(0); + // Insert the array node - std::string ssAccessName = ssName; std::string ssTOML; - if (GetType() == sdv::toml::ENodeType::node_array) - { + if (prParent.first->GetType() == sdv::toml::ENodeType::node_array) ssTOML = "[]"; - ssAccessName = "[" + std::to_string(std::min(uiIndex, GetCount())) + "]"; - } else - ssTOML = QuoteText(ssName, EQuoteRequest::smart_key) + " = []"; - if (InsertTOML(uiIndex, ssTOML, true) != EInsertResult::insert_success) + ssTOML = QuoteText(prParent.second, EQuoteRequest::smart_key) + " = []"; + auto prResult = prParent.first->InsertTOML(ptrInsertBefore, ssTOML, true); + if (prResult.first != EInsertResult::insert_success || prResult.second.empty()) return nullptr; // Return the inserted node. - return GetNodeDirect(ssAccessName); + return prResult.second.back().get(); } - sdv::IInterfaceAccess* CNodeCollection::InsertTable(uint32_t uiIndex, const sdv::u8string& ssName, - sdv::toml::INodeCollectionInsert::EInsertPreference ePreference) + sdv::IInterfaceAccess* CNodeCollection::InsertTable(/*in*/ const sdv::u8string& ssInsertBefore, + /*in*/ const sdv::u8string& ssName, /*in*/ sdv::toml::EInsertPreference ePreference) { + // Get the parent to inser this node into + auto ptrArray = Cast(); + auto prParent = SmartParentCreate(ssName, ptrArray && ptrArray->TableArray()); + if (!prParent.first) return nullptr; + + // Determine the node to insert before and make sure that the node is not occurring before the parent. + std::shared_ptr ptrInsertBefore = Direct(ssInsertBefore); + if (ptrInsertBefore && (ptrInsertBefore == prParent.first || ptrInsertBefore < prParent.first)) + ptrInsertBefore = prParent.first->Get(0); + // Insert inline or standard table - std::string ssAccessName = ssName; std::string ssTOML; - if (Inline() || ePreference == sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_inline) + if (prParent.first->Inline() || ePreference == sdv::toml::EInsertPreference::prefer_inline) { - if (GetType() == sdv::toml::ENodeType::node_array) - { + if (prParent.first->GetType() == sdv::toml::ENodeType::node_array) ssTOML = "{}"; - ssAccessName = "[" + std::to_string(std::min(uiIndex, GetCount())) + "]"; - } else - ssTOML = QuoteText(ssName, EQuoteRequest::smart_key) + " = {}"; + ssTOML = QuoteText(prParent.second, EQuoteRequest::smart_key) + " = {}"; } - else if (GetType() == sdv::toml::ENodeType::node_array) // Node is a table array + else if (prParent.first->GetType() == sdv::toml::ENodeType::node_array) // Node is a table array { ssTOML = "[[" + QuoteText(GetName(), EQuoteRequest::smart_key) + "]]"; - ssAccessName = "[" + std::to_string(std::min(uiIndex, GetCount())) + "]"; } else - ssTOML = "[" + QuoteText(ssName, EQuoteRequest::smart_key) + "]"; + ssTOML = "[" + QuoteText(prParent.second, EQuoteRequest::smart_key) + "]"; // Insert the table node - if (InsertTOML(uiIndex, ssTOML, true) != EInsertResult::insert_success) + auto prResult = prParent.first->InsertTOML(ptrInsertBefore, ssTOML, true); + if (prResult.first != EInsertResult::insert_success || prResult.second.empty()) return nullptr; // Return the inserted node. - return GetNodeDirect(ssAccessName); + return prResult.second.back().get(); } - sdv::IInterfaceAccess* CNodeCollection::InsertTableArray(uint32_t uiIndex, const sdv::u8string& ssName, - sdv::toml::INodeCollectionInsert::EInsertPreference ePreference) + sdv::IInterfaceAccess* CNodeCollection::InsertTableArray(/*in*/ const sdv::u8string& ssInsertBefore, + /*in*/ const sdv::u8string& ssName, /*in*/ sdv::toml::EInsertPreference ePreference) { - // Insert inline or standard table array - std::string ssAccessName = ssName; - std::string ssTOML; + // Get the parent to insert this node into + auto prParent = SmartParentCreate(ssName, true); + if (!prParent.first) return nullptr; - if (Inline() || ePreference == sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_inline) + // Determine the node to insert before and make sure that the node is not occurring before the parent. + std::shared_ptr ptrInsertBefore = Direct(ssInsertBefore); + if (ptrInsertBefore && (ptrInsertBefore == prParent.first || ptrInsertBefore < prParent.first)) + ptrInsertBefore = prParent.first->Get(0); + + // Insert inline or standard table array + std::string ssTOML; + if (prParent.first->Inline() || ePreference == sdv::toml::EInsertPreference::prefer_inline) { - if (GetType() == sdv::toml::ENodeType::node_array) - { + if (prParent.first->GetType() == sdv::toml::ENodeType::node_array) ssTOML = "[{}]"; - ssAccessName = "[" + std::to_string(std::min(uiIndex, GetCount())) + "][0]"; - } else { // If there is a table array with the same name already, the node will be added to the end. - auto ptrExistingNode = Direct(ssName); + auto ptrExistingNode = Direct(prParent.second); auto ptrExistingArray = ptrExistingNode ? ptrExistingNode->Cast() : std::shared_ptr(); - if (ptrExistingArray && !ptrExistingArray->TableArray()) - return nullptr; - size_t nCount = ptrExistingArray ? ptrExistingArray->GetCount() : 0; - ssTOML = QuoteText(ssName, EQuoteRequest::smart_key) + " = [{}]"; - ssAccessName += "[" + std::to_string(nCount) + "]"; + if (ptrExistingArray && !ptrExistingArray->TableArray()) return nullptr; + ssTOML = QuoteText(prParent.second, EQuoteRequest::smart_key) + " = [{}]"; } } else { // If there are table arrays, the node is added to the table array, but the order is determined by this node. Count the // amount of nodes before the insertion point. - ssTOML = "[[" + QuoteText(ssName, EQuoteRequest::smart_key) + "]]"; - size_t nTableArrayCnt = std::count_if(m_vecNodeOrder.begin(), - m_vecNodeOrder.begin() + std::min(static_cast(uiIndex), m_vecNodeOrder.size()), - [&](const std::shared_ptr& rptrNode) { return rptrNode->GetName() == ssName; }); - ssAccessName += "[" + std::to_string(nTableArrayCnt) + "]"; + ssTOML = "[[" + QuoteText(prParent.second, EQuoteRequest::smart_key) + "]]"; } // Insert the table node - if (InsertTOML(uiIndex, ssTOML, true) != EInsertResult::insert_success) + auto prResult = prParent.first->InsertTOML(ptrInsertBefore, ssTOML, true); + if (prResult.first != EInsertResult::insert_success || prResult.second.empty()) return nullptr; // Return the inserted node. - return GetNodeDirect(ssAccessName); + return prResult.second.back().get(); } - sdv::toml::INodeCollectionInsert::EInsertResult CNodeCollection::InsertTOML(uint32_t uiIndex, const sdv::u8string& ssTOML, - bool bRollbackOnPartly) + sdv::toml::INodeCollectionInsert::EInsertResult CNodeCollection::InsertTOML(/*in*/ const sdv::u8string& ssInsertBefore, + /*in*/ const sdv::u8string& ssTOML, /*in*/ bool bRollbackOnPartly) { + std::shared_ptr ptrInsertBefore = Direct(ssInsertBefore); + + return InsertTOML(ptrInsertBefore, ssTOML, bRollbackOnPartly).first; + } + + std::pair>> + CNodeCollection::InsertTOML(const std::shared_ptr& rptrInsertBefore,const sdv::u8string& ssTOML, + bool bRollbackOnPartly) + { + std::vector> vecRetNodes; try { + // Lock the rebuild of the node order + auto lock = Parser().CreateRebuildLockObject(); + // Parser for the new TOML code CParser parser; @@ -1157,7 +1329,7 @@ namespace toml_parser if (ptrArray) ptrCollection = ptrArray->Cast(); if (!ptrCollection) - return sdv::toml::INodeCollectionInsert::EInsertResult::insert_fail; + return std::make_pair(sdv::toml::INodeCollectionInsert::EInsertResult::insert_fail, vecRetNodes); } else { @@ -1193,13 +1365,13 @@ namespace toml_parser if (ptrNode->Inline()) ptrPotentialNewNode->Cast()->MakeInline(); else - ptrPotentialNewNode->Cast()->MakeStandard(); + ptrPotentialNewNode->Cast()->MakeStandard(true); continue; } // Node exists already. Add to duplicate name nodes vector (or fail if no all nodes should fit). if (bRollbackOnPartly) - return sdv::toml::INodeCollectionInsert::EInsertResult::insert_fail; + return std::make_pair(sdv::toml::INodeCollectionInsert::EInsertResult::insert_fail, vecRetNodes); vecDuplicateNameNodes.push_back(ptrPotentialNewNode); } @@ -1209,115 +1381,67 @@ namespace toml_parser // Any nodes left to insert? if (!ptrCollection->GetCount()) - return sdv::toml::INodeCollectionInsert::EInsertResult::insert_fail; + return std::make_pair(sdv::toml::INodeCollectionInsert::EInsertResult::insert_fail, vecRetNodes); // Add the child nodes to the collection (use a copy of the nodes list, since SetParentPtr changes the node list). auto lstCopyNodes = ptrCollection->m_lstNodes; - std::vector> vecSkipNodes; for (auto ptrNewNode : lstCopyNodes) { // If the node is a table array, it is allowed that the tables are added to an existing table array. auto ptrNewArray = ptrNewNode->Cast(); auto ptrExistingNode = Direct(ptrNewNode->GetName()); auto ptrExistingArray = ptrExistingNode ? ptrExistingNode->Cast() : std::shared_ptr(); - if (ptrNewArray && ptrNewArray->TableArray() && ptrExistingArray && - (ptrExistingArray->TableArray() || !ptrExistingArray->GetCount())) + + // Shift the node to the target list + if (!ptrExistingArray) { - // Determine the current index of the array. This is used to determine to insert the nodes before or behind - // the existing nodes within the array. - uint32_t uiCurrentPos = ptrExistingNode->GetIndex(); - - // Insert each table in the already existing table array. Assign the parent pointer and the location in the - // parent table array. - for (uint32_t uiNewArrayIndex = 0; uiNewArrayIndex < ptrNewArray->GetCount(); uiNewArrayIndex++) - { - auto ptrNewTable = ptrNewArray->Get(uiNewArrayIndex); - ptrNewTable->SetParentPtr(ptrExistingArray); - ptrExistingArray->m_vecNodeOrder.insert( - uiCurrentPos > uiIndex ? (ptrExistingArray->m_vecNodeOrder.begin() + uiNewArrayIndex) : - ptrExistingArray->m_vecNodeOrder.end(), - ptrNewTable); - } - - // Do not process the table array when processing the position of all nodes in the parsed TOML code. - vecSkipNodes.push_back(ptrNewNode); - continue; - } - - // Inserting values from an existing array into an array with the same name (which is not a table array) is not - // automatically supported. For this the array content needs to specifically be added to the existing array. - if (ptrExistingArray) - return sdv::toml::INodeCollectionInsert::EInsertResult::insert_fail; // Should not happen - - // Asign the new parent pointer. - if (ptrNewNode->Cast() && Inline()) - ptrNewNode->Cast()->MakeInline(); - ptrNewNode->SetParentPtr(Cast()); - } - - // Insert the nodes in the node vector at the required index. - size_t nTargetIndex = uiIndex; - for (uint32_t uiNewNodeIndex = 0; uiNewNodeIndex < ptrCollection->GetCount(); uiNewNodeIndex++) - { - auto ptrNewNode = ptrCollection->Get(uiNewNodeIndex); - - // Skip when the node is already inserted previously. - if (std::find(vecSkipNodes.begin(), vecSkipNodes.end(), ptrNewNode) != vecSkipNodes.end()) continue; - - // The target index should not supercede the size of the vector - if (nTargetIndex > GetCount()) - nTargetIndex = GetCount(); - - // If the node collection is - // - inline table or table array - // --> standard nodes will be converted to inline nodes. - // --> table-arrays can be added to additional table arrays - // - standard table or table array - // --> standard nodes will be inserted behind the inline nodes. - // --> inline nodes will be inserted before the standard nodes. - if (Inline()) - { - // Only inline nodes can be inserted in other inline nodes. - if (ptrNewNode->Cast()) + // Asign the new parent pointer. + if (ptrNewNode->Cast() && Inline()) ptrNewNode->Cast()->MakeInline(); + ptrNewNode->SetParentPtr(Cast()); + if (rptrInsertBefore) + ptrNewNode->NodeIndex().MoveBefore(rptrInsertBefore->NodeIndex()); + vecRetNodes.push_back(ptrNewNode); } else { - // Determine the begin of standard nodes in the node vector - size_t nBeginStandardIndex = 0; - for (; nBeginStandardIndex < m_vecNodeOrder.size(); nBeginStandardIndex++) - { - if (!m_vecNodeOrder[nBeginStandardIndex]->Inline()) - break; - } - - // Correct the target index if necessary - if (ptrNewNode->Inline()) - { - if (nTargetIndex > nBeginStandardIndex) - nTargetIndex = nBeginStandardIndex; - } - else - { - if (nTargetIndex < nBeginStandardIndex) - nTargetIndex = nBeginStandardIndex; - } + // Inserting values from an existing array into an array with the same name (which is not a table array) is not + // automatically supported. For this the array content needs to specifically be added to the existing array. + if (!ptrExistingArray->TableArray()) // Should not happen + return std::make_pair(sdv::toml::INodeCollectionInsert::EInsertResult::insert_fail, vecRetNodes); } - // Insert the node - m_vecNodeOrder.insert(m_vecNodeOrder.begin() + nTargetIndex, ptrNewNode); - - // Increase the target index for the next target - nTargetIndex++; + // Set the parent pointer for each table in the table array. + if (ptrNewArray && ptrNewArray->TableArray()) + { + // Insert each table in the already existing table array. Assign the parent pointer and the location in the + // parent table array. + uint32_t uiNewArrayIndex = 0; + while (uiNewArrayIndex < ptrNewArray->GetCount()) + { + auto ptrNewTable = ptrNewArray->Get(uiNewArrayIndex); + // Since the new array might have received a new parent (or in any case a new parser reference), assign the + // parent once more to also assign the parser reference. + // Shift the table to the target array (this removes it from the current array). + if (ptrExistingArray) + ptrNewTable->SetParentPtr(ptrExistingArray); + else + ++uiNewArrayIndex; + if (rptrInsertBefore) + ptrNewTable->NodeIndex().MoveBefore(rptrInsertBefore->NodeIndex()); + vecRetNodes.push_back(ptrNewTable); + } + } } - + // Return the result - return vecDuplicateNameNodes.empty() ? sdv::toml::INodeCollectionInsert::EInsertResult::insert_success : - sdv::toml::INodeCollectionInsert::EInsertResult::insert_partly_success; + return std::make_pair(vecDuplicateNameNodes.empty() ? sdv::toml::INodeCollectionInsert::EInsertResult::insert_success : + sdv::toml::INodeCollectionInsert::EInsertResult::insert_partly_success, vecRetNodes); } - catch (const sdv::toml::XTOMLParseException& /*rexcept*/) + catch (const sdv::toml::XTOMLParseException& rexcept) { - return sdv::toml::INodeCollectionInsert::EInsertResult::insert_fail; + std::cerr << rexcept.what() << std::endl; + return std::make_pair(sdv::toml::INodeCollectionInsert::EInsertResult::insert_fail, vecRetNodes); } } @@ -1336,6 +1460,26 @@ namespace toml_parser return bRet && CNode::DeleteNode(); } + bool CNodeCollection::Inline() const + { + // Will be overridden + return false; + } + + bool CNodeCollection::Inline(bool bInline, bool bIncludeChildren /*= true*/) + { + // Updating children, but only when inline or when include children flags has been set. + if (!bInline && !bIncludeChildren) return true; + + // Update the children + for (auto& rptrNode : m_lstNodes) + { + rptrNode->Inline(bInline, bIncludeChildren); + } + + return true; + } + bool CNodeCollection::CanMakeInline() const { // To make a node inline is always possible. @@ -1344,9 +1488,11 @@ namespace toml_parser bool CNodeCollection::MakeInline() { - if (Inline()) return true; + // Block rebuilding + auto lock = Parser().CreateRebuildLockObject(); + bool bRet = Inline(true); - if (bRet) AutomaticFormat(); + if (bRet) AutomaticFormat(false); return bRet; } @@ -1354,30 +1500,35 @@ namespace toml_parser { // To make a node as standard node, this is only possible when the parent is not inline. auto ptrParent = GetParentPtr(); - if (!ptrParent) return false; - if (ptrParent->Inline()) return false; + if (ptrParent && ptrParent->Inline()) + return false; + + // If this is the root node, check if one of the nodes can be made standard + if (Cast()) + { + bool bRet = false; + for (const auto& rptrNode : m_lstNodes) + bRet |= rptrNode->Cast() ? rptrNode->Cast()->CanMakeStandard() : false; + return bRet; + } + return true; } - bool CNodeCollection::MakeStandard() + bool CNodeCollection::MakeStandard(/*in*/ bool bIncludeChildren) { - if (!Inline()) return true; - bool bRet = Inline(false); - if (bRet) AutomaticFormat(); + // Block rebuilding + auto lock = Parser().CreateRebuildLockObject(); + + if (!CanMakeStandard()) + return false; + bool bRet = Inline(false, bIncludeChildren); + if (bRet) AutomaticFormat(false); return bRet; } bool CNodeCollection::DeleteNode(const std::shared_ptr& rptrNode) { - // Remove from the view (just in case). - RemoveFromView(rptrNode); - - // In case this is an array collection, it could still happen, that the node is in the vector. Explicitly remove the node - // from the vector. - auto itElementVec = std::find(m_vecNodeOrder.begin(), m_vecNodeOrder.end(), rptrNode); - if (itElementVec != m_vecNodeOrder.end()) - m_vecNodeOrder.erase(itElementVec); - // Find the element auto itElementLst = std::find(m_lstNodes.begin(), m_lstNodes.end(), rptrNode); if (itElementLst == m_lstNodes.end()) @@ -1389,171 +1540,12 @@ namespace toml_parser return true; } - bool CNodeCollection::InsertIntoView(uint32_t uiIndex, const std::shared_ptr& rptrNode) - { - // Check for vaidity - if (!rptrNode) return false; - if (Inline() && !rptrNode->Inline()) return false; // Cannot assign a standard node to an inline node - if (!IsDescendant(rptrNode)) return false; // Must descent directly from this node - - // Get the current view - auto ptrView = rptrNode->GetViewPtr(); - if (!ptrView) ptrView = rptrNode->GetParentPtr(); - if (!ptrView) return false; - - // Determine the start of the standard nodes in the vector. - uint32_t uiStartStandard = 0; - while (m_vecNodeOrder.size() > uiStartStandard && !m_vecNodeOrder[uiStartStandard]->IsStandard()) - ++uiStartStandard; - - // Determine the potential new index location. - uint32_t uiNewIndex = uiIndex; - if (rptrNode->IsStandard() && uiIndex < uiStartStandard) uiNewIndex = uiStartStandard; - else if (rptrNode->IsInline() && uiIndex > uiStartStandard) uiNewIndex = uiStartStandard; - if (static_cast(uiNewIndex) > m_vecNodeOrder.size()) uiNewIndex = static_cast(m_vecNodeOrder.size()); - - // Special case, if the current view is identical and the node will be located at the same position, do not do anything. - // This is necessary to keep the code snippets in place. Otherwise they might be relocated. - // Also, if the current position is before the new position, correct the new index (since we remove the current position). - if (ptrView == Cast()) - { - uint32_t uiCurrentIndex = rptrNode->GetIndex(); - if (uiCurrentIndex == uiNewIndex) return true; // Nothing to do... - if (uiCurrentIndex < uiNewIndex) uiNewIndex--; - if (uiCurrentIndex < uiStartStandard) uiStartStandard--; - } - - // Remove a previous view assignment (if necessary this will shift code snippets as well). - ptrView->RemoveFromView(rptrNode); - - // Check whether the node is already part of the view - if (std::find(m_vecNodeOrder.begin(), m_vecNodeOrder.end(), rptrNode) != m_vecNodeOrder.end()) - return true; // Insert only once. - - // Determine the first position and the beyond-last position - uint32_t uiFirst = rptrNode->IsStandard() ? uiStartStandard : 0; - uint32_t uiLast = rptrNode->IsStandard() ? static_cast(m_vecNodeOrder.size()) : uiStartStandard; - - // If the node is inserted at the first position, shift the out-of-scope code snippet from the first node. - if ((uiNewIndex <= uiFirst) && (uiFirst < m_vecNodeOrder.size())) - { - rptrNode->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_before) - .Insert(m_vecNodeOrder[uiFirst]->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_before)); - m_vecNodeOrder[uiFirst]->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_before).Clear(); - } - - // If the node is inserted at the last position, shift the out-of-scope code snippet from the last node. - if ((uiNewIndex >= uiLast) && (uiFirst < m_vecNodeOrder.size())) - { - rptrNode->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_behind) - .Append(m_vecNodeOrder[uiLast - 1]->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_behind)); - m_vecNodeOrder[uiLast - 1]->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_before).Clear(); - } - - // Insert the node - m_vecNodeOrder.insert(m_vecNodeOrder.begin() + uiNewIndex, rptrNode); - - return true; - } - - bool CNodeCollection::RemoveFromView(const std::shared_ptr& rptrNode) - { - // Check for validity - if (!rptrNode) return false; - if (rptrNode->GetViewPtr() != Cast()) return false; - - // Get the current position - uint32_t uiCurrentIndex = rptrNode->GetIndex(); - if (uiCurrentIndex >= m_vecNodeOrder.size()) return false; // Not present (likely already removed) - - // Determine the start of the standard nodes in the vector. - uint32_t uiStartStandard = 0; - while (m_vecNodeOrder.size() > uiStartStandard && !m_vecNodeOrder[uiStartStandard]->IsStandard()) - ++uiStartStandard; - - // Determine the first position and the beyond-last position - uint32_t uiFirst = rptrNode->IsStandard() ? uiStartStandard : 0; - uint32_t uiLast = rptrNode->IsStandard() ? static_cast(m_vecNodeOrder.size()) : uiStartStandard; - - // Move the out-of-scope code to another node - if ((uiLast - uiFirst) > 1) - { - // If removing the node and a node with the same classification (standard or inline) is still available, shift the - // out-of-scope code to the left-over nodes with the same classification. - if (uiCurrentIndex <= uiFirst) - { - // The first node will be removed - copy the out-of-scope code to the node behind - m_vecNodeOrder[uiCurrentIndex + 1]->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_before) - .Insert(rptrNode->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_behind)); - m_vecNodeOrder[uiCurrentIndex + 1]->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_before) - .Insert(rptrNode->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_before)); - } else if (uiCurrentIndex >= (uiLast - 1)) - { - // The last node will be removed - copy the out-of-scope code to the node before - m_vecNodeOrder[uiCurrentIndex - 1]->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_behind) - .Append(rptrNode->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_before)); - m_vecNodeOrder[uiCurrentIndex - 1]->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_behind) - .Append(rptrNode->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_behind)); - } else - { - // Copy the out-of-scope code from the end of the node to the node following - m_vecNodeOrder[uiCurrentIndex - 1]->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_behind) - .Append(rptrNode->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_before)); - m_vecNodeOrder[uiCurrentIndex + 1]->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_before) - .Insert(rptrNode->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_behind)); - } - } - else if (m_vecNodeOrder.size() > 1) - { - // If there are no more nodes with the same classification, but there are nodes with another classification, then move - // the out-of-scope code to the node with the other classification. - if (uiCurrentIndex == 0) - { - // The first node will be removed - copy the out-of-scope code to the node behind. - m_vecNodeOrder[uiCurrentIndex + 1]->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_before) - .Insert(rptrNode->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_behind)); - m_vecNodeOrder[uiCurrentIndex + 1]->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_before) - .Insert(rptrNode->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_before)); - } else - { - // The last node will be removed - copy the out-of-scope code to the node before. - m_vecNodeOrder[uiCurrentIndex - 1]->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_behind) - .Append(rptrNode->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_before)); - m_vecNodeOrder[uiCurrentIndex - 1]->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_behind) - .Append(rptrNode->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_behind)); - } - } - else if (IsStandard()) - { - // If there are no nodes left over, but this node is a standard node, move the out-of-scope code to this node. - CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_behind) - .Insert(rptrNode->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_behind)); - CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_behind) - .Insert(rptrNode->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_before)); - } - - // If this node is not a standard node, then the out-of-scope code cannot be maintained. - - // Delete the out-of-scope code. - rptrNode->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_before).Clear(); - rptrNode->CodeSnippet(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_behind).Clear(); - - // Remove the actual pointer. - rptrNode->SetViewPtr(nullptr); - - // And remove from the vector (but only when the collection is not an array). - if (!Cast()) - m_vecNodeOrder.erase(m_vecNodeOrder.begin() + uiCurrentIndex); - - return true; - } - uint32_t CNodeCollection::FindIndex(const std::shared_ptr& rptrNode) const { // Find the element - for (uint32_t uiIndex = 0; uiIndex < m_vecNodeOrder.size(); uiIndex++) + for (uint32_t uiIndex = 0; uiIndex < GetCount(); uiIndex++) { - if (rptrNode == m_vecNodeOrder[uiIndex]) + if (rptrNode == Get(uiIndex)) return uiIndex; } return sdv::toml::npos; @@ -1572,6 +1564,13 @@ namespace toml_parser return false; } + void CNodeCollection::ReassignParser(CParser& rParser) + { + CNode::ReassignParser(rParser); + for (auto& rptrNode : m_lstNodes) + rptrNode->ReassignParser(rParser); + } + CTable::CTable(CParser& rparser, const std::string& rssName, const std::string& rssRawName, bool bDefaultInline, bool bExplicit /*= true*/) : CNodeCollection(rparser, rssName, rssRawName), m_bDefinedExplicitly(bExplicit), m_bInline(bDefaultInline) @@ -1582,6 +1581,37 @@ namespace toml_parser return IsDeleted() ? sdv::toml::ENodeType::node_invalid : sdv::toml::ENodeType::node_table; } + uint32_t CTable::GetCount() const + { + return static_cast(m_vecNodeOrder.size()); + } + + std::shared_ptr CTable::Get(uint32_t uiIndex) const + { + if (static_cast(uiIndex) >= m_vecNodeOrder.size()) + return nullptr; + + return m_vecNodeOrder[uiIndex]; + } + + bool CTable::DeleteNode() + { + // Get parent + auto ptrParent = GetParentPtr(); + + // Check for inline + bool bInline = Inline(); + + // Delete the node + bool bRet = CNodeCollection::DeleteNode(); + + // If the parent node is an array and doesn't have any members any more and is not inline; remove the parent as well. + if (!bInline && ptrParent->GetType() == sdv::toml::ENodeType::node_array && !ptrParent->GetCount()) + ptrParent->DeleteNode(); + + return bRet; + } + std::string CTable::GenerateTOML(const CGenContext& rContext /*= CGenContext()*/) const { if (IsDeleted()) return {}; @@ -1602,7 +1632,7 @@ namespace toml_parser // Special case, table as part of table array - but only if the parent is included in the generation. This can be identified // by the top most flag of the context. bool bTableArray = false; - if (!contextCopy.TopMostNode() || contextCopy.CheckOption(toml_parser::EGenerateOptions::full_header)) + if (!contextCopy.TopMostNode() || contextCopy.CheckOption(EGenerateOptions::full_header)) bTableArray = !contextCopy.Embedded() && ptrParent && ptrParent->Cast() && ptrParent->Cast()->TableArray(); @@ -1614,7 +1644,7 @@ namespace toml_parser { std::shared_ptr ptrNode = Get(uiIndex); if (!ptrNode) continue; - if (bTableArray || !ptrNode->Cast() || ptrNode->Inline()) + if (bTableArray || (!ptrNode->Cast() && !ptrNode->Cast()) || ptrNode->Inline()) bDoNotPrint = false; } @@ -1651,9 +1681,8 @@ namespace toml_parser std::shared_ptr ptrNode = Get(uiIndex); if (!ptrNode) continue; - // If the node has a view pointer, which is not identical to this pointer, do not print the node... it will be printed - // by a different node. - if (!ptrNode->IsPartOfView(contextCopy, Cast())) continue; + // If the node is part of the view in the context, it is printed by the view node and not here. + if (!contextCopy.IsPartOfView(ptrNode)) continue; if (contextCopy.Inline() || ptrNode->Inline()) { @@ -1801,19 +1830,37 @@ namespace toml_parser bool CTable::Inline() const { - auto ptrParent = GetParentPtr(); - if (!ptrParent) return m_bInline; return m_bInline; } - bool CTable::Inline(bool bInline) + bool CTable::Inline(bool bInline, bool bIncludeChildren /*= true*/) { - if (bInline == m_bInline) return true; + // When inline, all children are also inline. + if (m_bInline && bInline) return true; // Nothing to do + + // When not inline and children are not to be considered, shortcut when possible + if (!m_bInline && !bInline && !bIncludeChildren) return true; // Nothing to do + + // Allowed to make standard + if (!Cast() && !bInline && !CanMakeStandard()) return false; // Nothing to do + + // If a parent if inline, the child needs to be inline as well. + bool bTargetInline = bInline; auto ptrParent = GetParentPtr(); if (ptrParent && ptrParent->Inline()) - m_bInline = true; - else - m_bInline = bInline; + bTargetInline = true; + + // If making inline, make children inline first (call to base class). + // If making standard, make standard before making children inline. + if (bTargetInline) + { + if (CNodeCollection::Inline(true)) + m_bInline = true; + } else + { + m_bInline = false; + CNodeCollection::Inline(false, bIncludeChildren); + } return m_bInline == bInline; } @@ -1833,6 +1880,9 @@ namespace toml_parser if (!rptrCollection) return false; // No collection supplied. if (rptrCollection == Cast()) return true; // Collections are identical, nothing to combine. if (rptrCollection->IsDescendant(shared_from_this())) return false; // Circular reference. + + // Lock the rebuild of the node order + auto lock = Parser().CreateRebuildLockObject(); // Run through the provided collection list // If nodes don't exist in the current collection, add the nodes @@ -1850,11 +1900,12 @@ namespace toml_parser auto ptrExistNode = Direct(ptrNewNode->GetName()); // Check whether the node is really new; if it is, add the node - // Exception to the rule... when the existing node is a table array and the node to add is a table, do not do replace the node. + // Exception to the rule... when the existing node is a table array and the node to add is a table, do not do replace + // the node. if (!ptrExistNode || (ptrNewNode->GetType() == sdv::toml::ENodeType::node_table && ptrExistNode->Cast() && ptrExistNode->Cast()->TableArray())) { - bResult &= InsertTOML(sdv::toml::npos, ptrNewNode->GenerateTOML(contextGeneration), true) == + bResult &= InsertTOML("", ptrNewNode->GenerateTOML(contextGeneration), true) == sdv::toml::INodeCollectionInsert::EInsertResult::insert_success; continue; } @@ -1862,11 +1913,10 @@ namespace toml_parser // The node exists. Are the node types identical? If not, replace the existing node with the new node if (ptrNewNode->GetType() != ptrExistNode->GetType()) { - uint32_t uiExistIndex = ptrExistNode->GetIndex(); bool bLocalResult = ptrExistNode->DeleteNode(); bResult &= bLocalResult; if (bLocalResult) - bResult &= InsertTOML(uiExistIndex, ptrNewNode->GenerateTOML(contextGeneration), true) == + bResult &= InsertTOML(ptrExistNode, ptrNewNode->GenerateTOML(contextGeneration), true).first == sdv::toml::INodeCollectionInsert::EInsertResult::insert_success; continue; } @@ -1897,6 +1947,9 @@ namespace toml_parser if (rptrCollection == Cast()) return false; // Collections are identical, this would empty the collection. if (rptrCollection->IsDescendant(shared_from_this())) return false; // Circular reference. + // Lock the rebuild of the node order + auto lock = Parser().CreateRebuildLockObject(); + // Run through the provided collection list. // If nodes don't exist in the current collection, leave the nodes in the collection. // If nodes exist in the current collection, but are different, leave the nodes in the collection. @@ -1910,7 +1963,7 @@ namespace toml_parser if (!ptrReductorNode) continue; auto ptrExistNode = Direct(ptrReductorNode->GetName()); - // Check whether the node is exists; if not, leave the node in the collection. + // Check whether the node exists; if not, leave the node in the collection. if (!ptrExistNode) continue; // Deal with table arrays... @@ -1948,7 +2001,37 @@ namespace toml_parser return bResult; } - CArray::CArray(CParser& rparser, const std::string& rssName, const std::string& rssRawName, + void CTable::RebuildNodeOrder(bool bForce) + { + // Is rebuild locked? Then do not rebuild. + if (!bForce && Parser().RebuildLocked()) return; + + m_vecNodeOrder.clear(); + FillNodeOrderVector(m_vecNodeOrder); + + // Sort the nodes + std::sort(m_vecNodeOrder.begin(), + m_vecNodeOrder.end(), + [&](const std::shared_ptr& rptrNode1, const std::shared_ptr& rptrNode2) -> bool + { + if (!rptrNode2) return true; + if (!rptrNode1) return false; + + // Check whether only one is inline. If so, node 1 is smaller when node 1 is inline. + if (rptrNode1->Inline() != rptrNode2->Inline()) + return rptrNode1->Inline(); + + // Check for the smallest index + return *rptrNode1 < *rptrNode2; + }); + + // Call base class + CNodeCollection::RebuildNodeOrder(bForce); + } + + CArray::CArray(CParser& rparser, + const std::string& rssName, + const std::string& rssRawName, bool bExplicitTableArray /*= false*/) : CNodeCollection(rparser, rssName, rssRawName), m_bDefinedExplicitly(bExplicitTableArray), m_bInline(!bExplicitTableArray) {} @@ -1995,6 +2078,80 @@ namespace toml_parser return ptrNode->Direct(ssSecond); } + std::pair, std::string> CArray::SmartParentCreate(const std::string& rssPath, + bool bInsertTableArray /*= false*/) + { + auto prKey = SplitNodeKey(rssPath); + + // Special situation: if the first part of the name is empty or not a number, this is not an error, but should cause the + // node to be added to the end of the array. + if (rssPath.empty()) return std::make_pair(Cast(), rssPath); + if (prKey.first.empty() || prKey.first.find_first_not_of("0123456789") != std::string::npos) + prKey = std::make_pair(std::to_string(GetCount()), rssPath); + uint32_t uiArrayIndex = 0; + std::from_chars(prKey.first.data(), prKey.first.data() + prKey.first.size(), uiArrayIndex); + + // Determine whether the node should be a table or an array. For this take the next name part and check for an index. + auto prKeyNext = SplitNodeKey(prKey.second); + sdv::toml::ENodeType eTargetType = + (!prKeyNext.first.empty() && prKeyNext.first.find_first_not_of("0123456789") != std::string::npos) + ? sdv::toml::ENodeType::node_table + : sdv::toml::ENodeType::node_array; + + // Find the node + std::shared_ptr ptrNode = Get(uiArrayIndex); + std::shared_ptr ptrNodeCollection; + if (ptrNode) + { + ptrNodeCollection = ptrNode->Cast(); + if (ptrNode->GetType() != eTargetType) + { + // If the node is an array and there is no index, take the last node in the array. + if (ptrNode->GetType() == sdv::toml::ENodeType::node_array && ptrNodeCollection->GetCount()) + { + ptrNode = ptrNodeCollection->Get(ptrNodeCollection->GetCount() - 1); + ptrNodeCollection = ptrNode->Cast(); + } else + return std::make_pair(nullptr, rssPath); + } + } + + // If not found, create the node collection + if (!ptrNodeCollection) + { + // Determine whether the next key part is a number - then create an array or whether the next key part is a name, then + // create a table. + if (eTargetType == sdv::toml::ENodeType::node_array) + { + auto* pArray = dynamic_cast(InsertArray("[" + std::to_string(uiArrayIndex) + "]", "")); + if (pArray) + ptrNodeCollection = pArray->Cast(); + } + else + { + // If this is an array, do not insert another table. Use the last node collection instead. + if (!bInsertTableArray && GetCount()) + { + ptrNode = Get(GetCount() - 1); + if (ptrNode) + ptrNodeCollection = ptrNode->Cast(); + } + else + { + auto* pTable = dynamic_cast( + InsertTable("[" + std::to_string(uiArrayIndex) + "]", "", sdv::toml::EInsertPreference::prefer_standard)); + if (pTable) + ptrNodeCollection = pTable->Cast(); + } + } + if (!ptrNodeCollection) + return std::make_pair(nullptr, rssPath); // Not found + } + + // Get or create next node + return ptrNodeCollection->SmartParentCreate(prKey.second, bInsertTableArray); + } + std::string CArray::GenerateTOML(const CGenContext& rContext /*= CGenContext()*/) const { if (IsDeleted()) return {}; @@ -2036,9 +2193,8 @@ namespace toml_parser std::shared_ptr ptrNode = Get(uiIndex); if (!ptrNode) continue; - // If the node has a view pointer, which is not identical to the this pointer, do not print the node... it will be - // printed by a different node. - if (!ptrNode->IsPartOfView(contextCopy, Cast())) + // If the node is part of the view in the context, it is printed by the view node and not here. + if (!contextCopy.IsPartOfView(ptrNode)) continue; // Generate the TOML for the array node. Copy the context with the full key path (when inline) or the key context of @@ -2128,6 +2284,7 @@ namespace toml_parser return; // Unexpected // Check the last node of the array for a potential comma in the code snippet following the node assignment. + RebuildNodeOrder(true); if (GetCount()) { auto ptrNode = Get(GetCount() - 1); @@ -2188,6 +2345,45 @@ namespace toml_parser return true; } + bool CArray::Inline() const + { + return m_bInline; + } + + bool CArray::Inline(bool bInline, bool bIncludeChildren /*= true*/) + { + // When inline, all children are also inline. + if (m_bInline && bInline) return true; // Nothing to do + + // When not inline and children are not to be considered, shortcut when possible + if (!m_bInline && !bInline && !bIncludeChildren) return true; // Nothing to do + + // Allowed to make standard + if (!bInline && !CanMakeStandard()) return false; // Nothing to do + + // If a parent if inline, the child needs to be inline as well. + bool bTargetInline = bInline; + auto ptrParent = GetParentPtr(); + if (ptrParent && ptrParent->Inline()) + bTargetInline = true; + + // If making inline, make children inline first (call to base class). + // If making standard, make standard before making children inline. + if (bTargetInline) + { + if (CNodeCollection::Inline(true)) + m_bInline = true; + } + else + { + // Make standard + m_bInline = false; + CNodeCollection::Inline(false, bIncludeChildren); + } + + return true; + } + bool CArray::CanMakeStandard() const { // Check with the collection @@ -2198,73 +2394,6 @@ namespace toml_parser return TableArray(); } - bool CArray::Inline() const - { - return m_bInline; - } - - bool CArray::Inline(bool bInline) - { - // Does anything change? - if (bInline == m_bInline) return true; - - // When making standard, the parent should not be inline. - auto ptrParent = GetParentPtr(); - if (!ptrParent) return false; - if (ptrParent->Inline() && !bInline) return false; - - // The array is inline per default. Only in the case of a table array (an array with only tables, and at least one table) - // the array could be standard. - if (!TableArray()) return false; - - // Check the order in the vector of the view or of the parent. When becoming inline, must be located before the standard - // nodes. When becoming standard, must be located behind the inline nodes. - // Special case for a table array: when becoming inline, remove all the tables from the view and add the array to the - // correct location. When becoming standard, remove the array from the location and add all the tables into the view. - m_bInline = bInline; - if (bInline) - { - // If becoming inline, remove the view and make all children inline as well. - uint32_t uiCurrentPos = sdv::toml::npos; - for (uint32_t uiIndex = 0; uiIndex < GetCount(); uiIndex++) - { - auto ptrNode = Get(uiIndex); - if (!ptrNode) continue; - if (uiCurrentPos == sdv::toml::npos) - uiCurrentPos = ptrNode->GetIndex(); - if (ptrNode->Cast()) - ptrNode->Cast()->MakeInline(); - InsertIntoView(uiIndex, ptrNode); - } - - // Move the array into the view of the parent. - ptrParent->InsertIntoView(uiCurrentPos, shared_from_this()); - } else - { - // Get the current position - uint32_t uiCurrentPos = GetIndex(); - - // Make all the children standard as well and move the into the view of the parent. - for (uint32_t uiIndex = 0; uiIndex < GetCount(); uiIndex++) - { - auto ptrNode = Get(uiIndex); - if (!ptrNode) continue; - if (uiCurrentPos == sdv::toml::npos) - uiCurrentPos = ptrNode->GetIndex(); - if (ptrNode->Inline() && ptrNode->Cast()) - ptrNode->Cast()->MakeStandard(); - ptrParent->InsertIntoView(uiCurrentPos, ptrNode); - if (uiCurrentPos != sdv::toml::npos) - ++uiCurrentPos; - } - - // Remove the array from the view of the parent - ptrParent->RemoveFromView(shared_from_this()); - } - - return true; - } - bool CArray::LastNodeWithSucceedingComma() const { return m_bLastChildNodeWithComma; @@ -2276,6 +2405,9 @@ namespace toml_parser if (rptrCollection == Cast()) return true; // Collections are identical, nothing to combine. if (rptrCollection->IsDescendant(shared_from_this())) return false; // Circular reference. + // Lock the rebuild of the node order + auto lock = Parser().CreateRebuildLockObject(); + // Differentiate between a table array and a normal array: // - A normal array should be identical. If not, take over the complete array. // - A table array might contain tables that are identical, extend with tables that are not. @@ -2285,7 +2417,7 @@ namespace toml_parser // In all other cases, update the content if necessary. bool bResult = true; - toml_parser::CGenContext contextGeneration; + CGenContext contextGeneration; contextGeneration.SetOption(EGenerateOptions::reduce_whitespace); contextGeneration.SetOption(EGenerateOptions::full_header); bool bTableArray = TableArray() && rptrCollection->Cast()->TableArray(); @@ -2295,11 +2427,10 @@ namespace toml_parser auto ptrNewNode = rptrCollection->Get(uiIndex); if (!ptrNewNode) continue; std::shared_ptr ptrExistNode; - uint32_t uiTargetIndex = uiIndex; if (bTableArray) { // Go through the current array and compare the generate TOML string for identical values. - toml_parser::CGenContext contextComparison; + CGenContext contextComparison; contextComparison.SetOption(EGenerateOptions::no_comments); std::string ssNewNodeTOML = ptrNewNode->GenerateTOML(contextComparison); bool bIdentical = false; @@ -2311,17 +2442,14 @@ namespace toml_parser bIdentical = ssNewNodeTOML == ssExistTOML; } if (!bIdentical) - { - uiTargetIndex = sdv::toml::npos; // New node will be added at the end of the array. ptrExistNode.reset(); - } } else ptrExistNode = Get(uiIndex); // Check whether there is an existing node. If not, add the node. if (!ptrExistNode) { - bResult &= InsertTOML(uiTargetIndex, ptrNewNode->GenerateTOML(contextGeneration), true) == + bResult &= InsertTOML("", ptrNewNode->GenerateTOML(contextGeneration), true) == sdv::toml::INodeCollectionInsert::EInsertResult::insert_success; continue; } @@ -2333,8 +2461,8 @@ namespace toml_parser bool bLocalResult = ptrExistNode->DeleteNode(); bResult &= bLocalResult; if (bLocalResult) - bResult &= InsertTOML(uiExistIndex, ptrNewNode->GenerateTOML(contextGeneration), true) == - sdv::toml::INodeCollectionInsert::EInsertResult::insert_success; + bResult &= InsertTOML(Get(uiExistIndex), ptrNewNode->GenerateTOML(contextGeneration), true).first + == sdv::toml::INodeCollectionInsert::EInsertResult::insert_success; continue; } @@ -2375,13 +2503,16 @@ namespace toml_parser if (rptrCollection == Cast()) return false; // Collections are identical, this would empty the collection. if (rptrCollection->IsDescendant(shared_from_this())) return false; // Circular reference. + // Lock the rebuild of the node order + auto lock = Parser().CreateRebuildLockObject(); + // DIfferentiate between a table array and a normal array: // - A normal array should be identical. If so, remove the complete array. // - A table array might contain tables that are identical, those are removed. All others stay. // For normal array, simply compare the generate TOML strings. If identical, remove the array content. bool bResult = true; - toml_parser::CGenContext context; + CGenContext context; context.SetOption(EGenerateOptions::no_comments); if (!TableArray() || !rptrCollection->Cast()->TableArray()) { diff --git a/sdv_services/core/toml_parser/parser_node_toml.h b/sdv_services/core/toml_parser/parser_node_toml.h index 1d99701..943e154 100644 --- a/sdv_services/core/toml_parser/parser_node_toml.h +++ b/sdv_services/core/toml_parser/parser_node_toml.h @@ -26,6 +26,7 @@ #include #include "miscellaneous.h" #include "code_snippet.h" +#include "parser_node_indexer.h" /// The TOML parser namespace namespace toml_parser @@ -72,11 +73,12 @@ namespace toml_parser void InitTopMostNode(const std::shared_ptr& rptrNode); /** - * @brief Check whether the provided node is a parent of the top most node. - * @param[in] rptrNode Reference to the node to use for the checking. - * @return Returns true if the node is a parent of the top most node, false otherwise. + * @brief A node is part of the view if either it is an inline node, or it is a standard node and the current parent is the + * root view. + * @param[in] rptrNode Reference to the smart pointer to the node to check for. + * @return Returns whether the node is part of the view. */ - bool PartOfExcludedParents(const std::shared_ptr& rptrNode) const; + bool IsPartOfView(const std::shared_ptr& rptrNode) const; /** * @brief Create a copy of the context class with a new key context. @@ -289,10 +291,42 @@ namespace toml_parser END_SDV_INTERFACE_MAP() /** + * @{ * @brief Get a reference to the TOML parser that generated this node. * @return Reference to the TOML parse. */ CParser& Parser(); + const CParser& Parser() const; + /** + * @} + */ + + /** + * @{ + * @brief Return the index object of the node. + * @return Reference to the node index object. + */ + const CNodeIndex& NodeIndex() const; + CNodeIndex& NodeIndex(); + /** + * @} + */ + + /** + * @brief Compare the index position of this node with the position of another node and return whether this node occurs + * before the other node. + * @param[in] rNode Reference to the node to compare the position with. + * @return Returns whether this node occurs before the other node. + */ + bool operator<(const CNode& rNode) const; + + /** + * @brief Compare the index position of this node with the position of another node and return whether this node occurs + * before the other node. + * @param[in] rptrNode Reference to the node to compare the position with. + * @return Returns whether this node occurs before the other node. + */ + bool operator<(const std::shared_ptr& rptrNode) const; /** * @brief Get the node name (no conversion to a literal or quoted key is made). Overload of sdv::toml::INodeInfo::GetName. @@ -328,9 +362,8 @@ namespace toml_parser virtual sdv::any_t GetValue() const override; /** - * @brief Get the index of this node within the view collection (either the assigned view or the parent). Overload of - * sdv::toml::INodeInfo::GetIndex. - * @return The index of the node within the view collection node or npos when no parent is available. + * @brief Get the index of this node within the parent collection. Overload of sdv::toml::INodeInfo::GetIndex. + * @return The index of the node within the parent collection node or npos when no parent is available. */ virtual uint32_t GetIndex() const override; @@ -370,10 +403,10 @@ namespace toml_parser virtual sdv::u8string GetComment(sdv::toml::INodeInfo::ECommentType eType) override; /** - * @brief Format the node automatically. This will remove the whitespace between the elements within the node. Comments - * will not be changed. Overload of sdv::toml::INodeInfo::AutomaticFormat. + * @brief Format the node automatically, remove redundant whitespace. Overload of sdv::toml::INodeInfo::AutomaticFormat. + * @param[in] bRemoveComments When set, the comments are removed from the node. */ - virtual void AutomaticFormat() override; + virtual void AutomaticFormat(/*in*/ bool bRemoveComments) override; /** * @brief Is the node inline? Overload of sdv::toml::INodeInfo::IsInline. @@ -457,10 +490,11 @@ namespace toml_parser std::shared_ptr Cast() const; /** - * @brief Set the parent node. + * @brief Reassign the parent node and if necessary the parser reference. + * @details This function allows shifting nodes from one parser to another. * @param[in] rptrParent Reference to the node to assign to this node as a parent. */ - void SetParentPtr(const std::shared_ptr& rptrParent); + virtual void SetParentPtr(const std::shared_ptr& rptrParent); /** * @brief Gets the parent node pointer. @@ -475,29 +509,6 @@ namespace toml_parser */ std::string GetParentPath() const; - /** - * @brief Set the view definition node. The view definition node is a parent or grand parent that presents the node when - * generating TOML code. When not set, the parent node is taking over this role. - * @param[in] rptrView Reference to the node to assign to this node as a parent or grand parent. - */ - void SetViewPtr(const std::shared_ptr& rptrView); - - /** - * @brief Gets the view definition node pointer. - * @return Returns the store view definition node pointer or an empty pointer when no view was assigned. - */ - std::shared_ptr GetViewPtr() const; - - /** - * @brief Checks whether the node is part of the view. - * @details The node is part of the view if the supplied pointer is identical to the view definition pointer, when the view - * definition pointer is not part of a parent of the topmost node. In all other cases, the node is part of the view. - * @param[in] rContext Reference to the context class to use during TOML code generation. - * @param[in] rptrNode Reference to the node to check whether it registered for a view. - * @return Returns whether this node is part of the view with the supplied pointer. - */ - bool IsPartOfView(const CGenContext& rContext, const std::shared_ptr& rptrNode) const; - /** * @brief Accesses a node by its key in the parse tree. * @details Elements of tables can be accessed and traversed by using '.' to separated the parent name from child name. @@ -507,7 +518,7 @@ namespace toml_parser * @attention Array indexing starts with 0! * @attention For an array, when no indexing is supplied, the latest entry will be returned. * @param[in] rssPath The path of the node to searched for. - * @return Returns a shared pointer to the wanted Node if it was found or a node with invalid content if it was not found. + * @return Returns a shared pointer to the wanted node if it was found or a node with invalid content if it was not found. */ virtual std::shared_ptr Direct(const std::string& rssPath) const = 0; @@ -577,13 +588,20 @@ namespace toml_parser */ std::string GetCustomPath(const std::string& rssPrefixKey, const std::string& rssContext) const; + /** + * @brief When the parent changes (e.g. when moving items from one parser to the other), the items and all its sub-items + * need to reasign the parser. + * @param[in] rParser Reference to the parser to assign. + */ + virtual void ReassignParser(CParser& rParser); + private: - std::weak_ptr m_ptrParent; ///< Weak pointer to the parent node (if existing). - std::weak_ptr m_ptrView; ///< Weak pointer to the view node (if existing and explicitly set). - std::string m_ssName; ///< Name of the node. - std::string m_ssRawName; ///< Raw name of the node. - bool m_bDeleted = false; ///< Enabled when the node was marked for deletion. - CParser& m_rParser; ///< Reference to the TOML parser. + CNodeIndex m_index; ///< Node index object holding the overall node position. + std::weak_ptr m_ptrParent; ///< Weak pointer to the parent node (if existing). + std::string m_ssName; ///< Name of the node. + std::string m_ssRawName; ///< Raw name of the node. + bool m_bDeleted = false; ///< Enabled when the node was marked for deletion. + std::reference_wrapper m_refParser; ///< Reference to the TOML parser. std::vector> m_vecCodeSnippets; ///< Vector with comments/code snippets. public: @@ -596,11 +614,16 @@ namespace toml_parser /** * @brief With some node collections it is possible to switch between inline and normal. * @remarks Additional node composition information will be removed and the order within the parent node might be changed. + * @remarks When made inline, all child nodes must be made inline as well. When made standard, only this node is made + * standard. + * @remarks Making this node a standard node, this is only possible when the parent is not an inline mode. * @param[in] bInline When set, try to switch to inline. Otherwise try to switch to normal. + * @param[in] bIncludeChildren When set and bInline is not set, applicable child nodes are converted as well (only tables + * and table-arrays can be defined as standard). Making a node inline is always including the children. * @return Returns whether the switch was successful. A switch to the same type (normal to normal or inline to inline is * always successful). When returning false, the switching might not be supported for this type. */ - virtual bool Inline(bool bInline) = 0; + virtual bool Inline(bool bInline, bool bIncludeChildren = true) = 0; /** * @brief Checks whether the table was explicitly defined. @@ -654,10 +677,12 @@ namespace toml_parser * @brief With some node collections it is possible to switch between inline and normal. Overload of CNode::Inline. * @remarks Additional node composition information will be removed and the order within the parent node might be changed. * @param[in] bInline When set, try to switch to inline. Otherwise try to switch to normal. + * @param[in] bIncludeChildren When set and bInline is not set, applicable child nodes are converted as well (only tables + * and table-arrays can be defined as standard). Making a node inline is always including the children. * @return Returns whether the switch was successful. A switch to the same type (normal to normal or inline to inline is * always successful). When returning false, the switching might not be supported for this type. */ - virtual bool Inline(bool bInline) override; + virtual bool Inline(bool bInline, bool bIncludeChildren = true) override; /** * @brief Accesses a node by its key in the parse tree. Overload of CNode::Direct. @@ -668,7 +693,7 @@ namespace toml_parser * @attention Array indexing starts with 0! * @attention For an array, when no indexing is supplied, the latest entry will be returned. * @param[in] rssPath The path of the node to searched for. - * @return Returns a shared pointer to the wanted Node if it was found or a node with invalid content if it was not found. + * @return Returns a shared pointer to the wanted node if it was found or a node with invalid content if it was not found. */ virtual std::shared_ptr Direct(const std::string& rssPath) const override; @@ -943,10 +968,10 @@ namespace toml_parser END_SDV_INTERFACE_MAP() /** - * @brief Format the node automatically. This will remove the whitespace between the elements within the node. Comments - * will not be changed. Overload of sdv::toml::INodeInfo::AutomaticFormat. + * @brief Format the node automatically, remove redundant whitespace. Overload of sdv::toml::INodeInfo::AutomaticFormat. + * @param[in] bRemoveComments When set, the comments are removed from the node. */ - virtual void AutomaticFormat() override; + virtual void AutomaticFormat(/*in*/ bool bRemoveComments) override; /** * @brief Returns the amount of nodes. Overload of sdv::toml::INodeCollection::GetCount. @@ -966,7 +991,28 @@ namespace toml_parser * @param[in] uiIndex Index of the node to get. * @return Smart pointer to the node object. */ - std::shared_ptr Get(uint32_t uiIndex) const; + virtual std::shared_ptr Get(uint32_t uiIndex) const; + + /** + * @brief After every insert, deletion and shift, the node order of this and all sub- tables need to be rebuild. + * @param[in] bForce Force rebuild, even if locked. + */ + virtual void RebuildNodeOrder(bool bForce); + + /** + * @brief After every insert, deletion and shift, the node order needs to be rebuild. + * @details Adding is done iteratively through the node list. In case the top level flag is set, all nodes are added that + * are marked explicit or if a node is implicit, the explicit sub-nodes are added. In case this is root view, all sub-nodes + * which are standard tables or standard table arrays are added. This flattens the hierarchy. When the root view is not set, + * only the tables and table arrays are added that are a direct child of the node collection. At the end of building the + * root view, the nodes are sorted using their index. Furthermore, inline nodes are moved to the beginning of the vector and + * standard nodes to the end. + * @param[in, out] rvecNodes Reference to the vector being filled with the nodes for the view. + * @param[in] bRootView Set when the count is top level. + * @param[in] bTopLevel Set when this is the top level node for adding sub nodes. + */ + virtual void FillNodeOrderVector(std::vector>& rvecNodes, bool bRootView = true, + bool bTopLevel = true); /** * @brief Accesses a node by its key in the parse tree. Overload of CNode::Direct. @@ -977,7 +1023,7 @@ namespace toml_parser * @attention Array indexing starts with 0! * @attention For an array, when no indexing is supplied, the latest entry will be returned. * @param[in] rssPath The path of the node to searched for. - * @return Returns a shared pointer to the wanted Node if it was found or a node with invalid content if it was not found. + * @return Returns a shared pointer to the wanted node if it was found or a node with invalid content if it was not found. */ virtual std::shared_ptr Direct(const std::string& rssPath) const override; @@ -993,85 +1039,124 @@ namespace toml_parser */ virtual sdv::IInterfaceAccess* GetNodeDirect(/*in*/ const sdv::u8string& ssPath) const override; + /** + * @brief Get or create the parent nodes automotatically from the path. + * @details Elements of tables can be accessed and traversed by using '.' to separated the parent name from child name. + * E.g. 'parent.child' would access the 'child' element of the 'parent' table. Elements of arrays can be accessed and + * traversed by using the index number in brackets. E.g. 'array[3]' would access the fourth element of the array 'array'. + * These access conventions can also be chained like 'table.array[2][1].subtable.integerElement'. + * @attention Array indexing starts with 0! + * @attention For an array, when no indexing or a too large index number is supplied, a new entry will be created and + * returned. + * @param[in] rssPath The path of the node to searched for. + * @param[in] bInsertTableArray Since a table array consists of an array and a table, this is different than the other + * insertions that only insert one element. Some special treatment is needed at certain points. + * @return Returns a pair with the shared pointer to the parent node and the leftover name. If the creation could not be + * done, a NULL pointer is returned. + */ + virtual std::pair, std::string> SmartParentCreate(const std::string& rssPath, + bool bInsertTableArray = false); + /** * @brief Insert a value into the collection at the location before the supplied index. Overload of * sdv::toml::INodeCollectionInsert::InsertValue. - * @param[in] uiIndex The insertion location to insert the node before. Can be npos or any value larger than the - * collection count to insert the node at the end of the collection. Value nodes cannot be inserted behind external - * tables and table arrays. If the index is referencing a position behind an external table or a table array, the index - * is automatically corrected. - * @param[in] ssName Name of the node to insert. Will be ignored for an array collection. The name must adhere to the - * key names defined by the TOML specification. Defining the key multiple times is not allowed. Quotation of key names - * is done automatically; the parser decides itself whether the key is bare-key, a literal key or a quoted key. - * @param[in] anyValue The value of the node, being either an integer, floating point number, virtual bool value or a string. + * @remarks In TOML, inline nodes are located before standard nodes. Since values are presented as inline node, they + * will be inserted before any standard node (table or table array if defined as standard node). + * @param[in] ssInsertBefore Name of the node to insert the value before. In case of an array, can be an index between + * square brackets. Can be empty, causing the node to be inserted at the end. + * @param[in] ssName Name of the node to insert. This name can contain parent nodes, which are automatically created if + * not existing. With arrays, since the value in TOML doesn't have a name, the name of the value must be an empty + * string and any names provided are considered parent nodes. The name must adhere to the key names defined by the TOML + * specification. Defining the key multiple times is not allowed. Quotation of key names is done automatically; the + * parser decides itself whether the key is bare-key, a literal key or a quoted key. + * @param[in] anyValue The value of the node, being either an integer, floating point number, virtual bool value or a + * string. * Conversion is automatically done to int64, double float, bool or u8string. * @return On success the interface to the newly inserted node is returned or NULL otherwise. */ - virtual sdv::IInterfaceAccess* InsertValue(uint32_t uiIndex, const sdv::u8string& ssName, sdv::any_t anyValue) override; + virtual sdv::IInterfaceAccess* InsertValue(/*in*/ const sdv::u8string& ssInsertBefore, /*in*/ const sdv::u8string& ssName, + /*in*/ sdv::any_t anyValue) override; /** * @brief Insert an array into the collection at the location before the supplied index. Overload of * sdv::toml::INodeCollectionInsert::InsertArray. - * @param[in] uiIndex The insertion location to insert the node before. Can be npos or any value larger than the - * collection count to insert the node at the end of the collection. Array nodes cannot be inserted behind external - * tables and table arrays. If the index is referencing a position behind an external table or a table array, the index - * is automatically corrected. - * @param[in] ssName Name of the array node to insert. Will be ignored if the current node is also an array collection. - * The name must adhere to the key names defined by the TOML specification. Defining the key multiple times is not - * allowed. Quotation of key names is done automatically; the parser decides itself whether the key is bare-key, a - * literal key or a quoted key. + * @remarks In TOML, inline nodes are located before standard nodes. Since arrays are presented as inline node, they + * will be inserted before any standard node (table or table array if defined as standard node). + * @param[in] ssInsertBefore Name of the node to insert the value before. In case of an array, can be an index between + * square brackets. Can be empty, causing the node to be inserted at the end. + * @param[in] ssName Name of the node to insert. This name can contain parent nodes, which are automatically created if + * not existing. With arrays, since the value in TOML doesn't have a name, the name of the value must be an empty + * string and any names provided are considered parent nodes. The name must adhere to the key names defined by the TOML + * specification. Defining the key multiple times is not allowed. Quotation of key names is done automatically; the + * parser decides itself whether the key is bare-key, a literal key or a quoted key. * @return On success the interface to the newly inserted node is returned or NULL otherwise. */ - virtual sdv::IInterfaceAccess* InsertArray(uint32_t uiIndex, const sdv::u8string& ssName) override; + virtual sdv::IInterfaceAccess* InsertArray(/*in*/ const sdv::u8string& ssInsertBefore, + /*in*/ const sdv::u8string& ssName) override; /** * @brief Insert a table into the collection at the location before the supplied index. Overload of * sdv::toml::INodeCollectionInsert::InsertTable. - * @param[in] uiIndex The insertion location to insert the node before. Can be npos or any value larger than the - * collection count to insert the node at the end of the collection. Table nodes cannot be inserted before value nodes - * or arrays. If the index is referencing a position before a value node or an array, the index is automatically - * corrected. - * @param[in] ssName Name of the table node to insert. Will be ignored if the current node is an array collection. - * The name must adhere to the key names defined by the TOML specification. Defining the key multiple times is not - * allowed. Quotation of key names is done automatically; the parser decides itself whether the key is bare-key, a - * literal key or a quoted key. + * @remarks In TOML, inline nodes are located before standard nodes. Tables can be inserted as inline node, in which + * case they will be inserted before any standard node (table or table array if defined as standard node). + * @param[in] ssInsertBefore Name of the node to insert the value before. In case of an array, can be an index between + * square brackets. Can be empty, causing the node to be inserted at the end. + * @param[in] ssName Name of the node to insert. This name can contain parent nodes, which are automatically created if + * not existing. With arrays, since the value in TOML doesn't have a name, the name of the value must be an empty + * string and any names provided are considered parent nodes. The name must adhere to the key names defined by the TOML + * specification. Defining the key multiple times is not allowed. Quotation of key names is done automatically; the + * parser decides itself whether the key is bare-key, a literal key or a quoted key. * @param[in] ePreference The preferred form of the node to be inserted. * @return On success the interface to the newly inserted node is returned or NULL otherwise. */ - virtual sdv::IInterfaceAccess* InsertTable(uint32_t uiIndex, const sdv::u8string& ssName, - sdv::toml::INodeCollectionInsert::EInsertPreference ePreference) override; + virtual sdv::IInterfaceAccess* InsertTable(/*in*/ const sdv::u8string& ssInsertBefore, /*in*/ const sdv::u8string& ssName, + /*in*/ sdv::toml::EInsertPreference ePreference) override; /** * @brief Insert a table array into the collection at the location before the supplied index. Overload of * sdv::toml::INodeCollectionInsert::InsertTableArray. - * @param[in] uiIndex The insertion location to insert the node before. Can be npos or any value larger than the - * collection count to insert the node at the end of the collection. Table array nodes cannot be inserted before value - * nodes or arrays. If the index is referencing a position before a value node or an array, the index is automatically - * corrected. - * @param[in] ssName Name of the array node to insert. Will be ignored if the current node is also an array collection. - * The name must adhere to the key names defined by the TOML specification. Defining the key multiple times is not - * allowed. Quotation of key names is done automatically; the parser decides itself whether the key is bare-key, a - * literal key or a quoted key. + * @remarks In TOML, inline nodes are located before standard nodes. Table arrays can be inserted as inline node, in + * which case they will be inserted before any standard node (table or table array if defined as standard node). + * @param[in] ssInsertBefore Name of the node to insert the value before. In case of an array, can be an index between + * square brackets. Can be empty, causing the node to be inserted at the end. + * @param[in] ssName Name of the node to insert. This name can contain parent nodes, which are automatically created if + * not existing. With arrays, since the value in TOML doesn't have a name, the name of the value must be an empty + * string and any names provided are considered parent nodes. The name must adhere to the key names defined by the TOML + * specification. Defining the key multiple times is not allowed. Quotation of key names is done automatically; the + * parser decides itself whether the key is bare-key, a literal key or a quoted key. * @param[in] ePreference The preferred form of the node to be inserted. * @return On success the interface to the newly inserted node is returned or NULL otherwise. */ - virtual sdv::IInterfaceAccess* InsertTableArray(uint32_t uiIndex, const sdv::u8string& ssName, - sdv::toml::INodeCollectionInsert::EInsertPreference ePreference) override; + virtual sdv::IInterfaceAccess* InsertTableArray(/*in*/ const sdv::u8string& ssInsertBefore, /*in*/ const sdv::u8string& ssName, + /*in*/ sdv::toml::EInsertPreference ePreference) override; /** * @brief Insert a TOML string as a child of the current collection node. If the collection is a table, the TOML string * should contain values and inline/external/array-table nodes with names. If the collection is an array, the TOML * string should contain and inline table nodes without names. Overload of sdv::toml::INodeCollectionInsert::InsertTOML. - * @param[in] uiIndex The insertion location to insert the node before. Can be npos or any value larger than the - * collection count to insert the node at the end of the collection. Table array nodes cannot be inserted before value - * nodes or arrays. If the index is referencing a position before a value node or an array, the index is automatically - * corrected. + * @remarks In TOML, inline nodes are located before standard nodes. Dependable on the nodes defined in the TOML they + * might be transferred to inline or they might be inserted at a different location. + * @param[in] ssInsertBefore Name of the node to insert the value before. In case of an array, can be an index between + * square brackets. Can be empty, causing the node to be inserted at the end. * @param[in] ssTOML The TOML string to insert. * @param[in] bRollbackOnPartly If only part of the nodes could be inserted, no node will be inserted. * @return The result of the insertion. */ - virtual sdv::toml::INodeCollectionInsert::EInsertResult InsertTOML(uint32_t uiIndex, const sdv::u8string& ssTOML, - bool bRollbackOnPartly) override; + virtual sdv::toml::INodeCollectionInsert::EInsertResult InsertTOML(/*in*/ const sdv::u8string& ssInsertBefore, + /*in*/ const sdv::u8string& ssTOML, /*in*/ bool bRollbackOnPartly) override; + + /** + * @brief Insert a TOML string as a child of the current collection node. If the collection is a table, the TOML string + * should contain values and inline/external/array-table nodes with names. If the collection is an array, the TOML + * string should contain and inline table nodes without names. + * @param[in] rptrInsertBefore The node to insert the TOML nodes before (if possible). Can be NULL, causing the TOML nodes + * to be inserted at the end. + * @param[in] ssTOML The TOML string to insert. + * @param[in] bRollbackOnPartly If only part of the nodes could be inserted, no node will be inserted. + * @return A pair structure with the result of the insertion and a vector of all the inserted nodes. + */ + std::pair>> InsertTOML( + const std::shared_ptr& rptrInsertBefore, const sdv::u8string& ssTOML, bool bRollbackOnPartly); /** * @brief Delete the current node. Overload of sdv::toml::INodeUpdate::DeleteNode. @@ -1080,6 +1165,23 @@ namespace toml_parser */ virtual bool DeleteNode() override; + /** + * @brief The derived class from the node collection can be inline or not. Overload of CNode::Inline. + * @return Returns whether the node is an inline node. + */ + virtual bool Inline() const override; + + /** + * @brief With some node collections it is possible to switch between inline and normal. Overload of CNode::Inline. + * @remarks Additional node composition information will be removed and the order within the parent node might be changed. + * @param[in] bInline When set, try to switch to inline. Otherwise try to switch to normal. + * @param[in] bIncludeChildren When set and bInline is not set, applicable child nodes are converted as well (only tables + * and table-arrays can be defined as standard). Making a node inline is always including the children. + * @return Returns whether the switch was successful. A switch to the same type (normal to normal or inline to inline is + * always successful). When returning false, the switching might not be supported for this type. + */ + virtual bool Inline(bool bInline, bool bIncludeChildren = true) override; + /** * @brief Can the node convert to an inline definition? Overload of sdv::toml::INodeCollectionConvert::CanMakeInline. * @return Returns whether the conversion to inline is possible. Returns 'true' when the node is already inline. @@ -1101,10 +1203,12 @@ namespace toml_parser /** * @brief Convert the node to a standard node. Overload of sdv::toml::INodeCollectionConvert::MakeStandard. + * @param[in] bIncludeChildren When set, applicable child nodes are made are converted to standard nodes as well (only + * tables and table-arrays can be defined as standard). * @return Returns whether the conversion was successful. Returns 'true' when the node was already defined as standard * node. */ - virtual bool MakeStandard() override; + virtual bool MakeStandard(/*in*/ bool bIncludeChildren) override; /** * @brief Delete a node from the collection. @@ -1114,28 +1218,6 @@ namespace toml_parser */ bool DeleteNode(const std::shared_ptr& rptrNode); - /** - * @brief Insert the node into the view. - * @remarks The node must be a descendant (direct child or an indirect child) of the node. - * @details Insert the node into the vector (and remove it from any previous vector if still assigned). Inline nodes can be - * assigned to stadard and inline nodes. Standard nodes can only be assigned to standard nodes. In the vector, first the - * inline nodes and then the standard nodes are located. This means that an inline node will be placed before standard nodes - * and standard nodes behind inline nodes, regardless of the index provided. - * @param[in] uiIndex Location within the vector to insert the node. Could be sdv::toml::npos to insert as last node. - * @param[in] rptrNode Reference to the smart pointer to the node to set the view for. If the node is not a direct child - * node, the view pointer of the node will be set. - * @return Returns true when the insertion was successful, false when node (likely because the node to insert is a standard - * node, whereas this node is an inline node. - */ - bool InsertIntoView(uint32_t uiIndex, const std::shared_ptr& rptrNode); - - /** - * @brief Remove a node from a view. - * @param[in] rptrNode Reference to the smart pointer pointing to the node to remove. - * @return Returns whether the removal was successful. - */ - bool RemoveFromView(const std::shared_ptr& rptrNode); - /** * @brief Find the index belonging to the provided node. * @param[in] rptrNode Reference to the smart pointer holding the node to return the index for. @@ -1151,7 +1233,7 @@ namespace toml_parser bool IsDescendant(const std::shared_ptr& rptrNode) const; /** - * @brief Generic inserting function for nodes. + * @brief Generic add function for nodes. * @details Elements of tables can be accessed and traversed by using '.' to separated the parent name from child name. * E.g. 'parent.child' would access the 'child' element of the 'parent' table. Elements of arrays can be accessed and * traversed by using the index number in brackets. E.g. 'array[3]' would access the fourth element of the array 'array'. @@ -1160,10 +1242,6 @@ namespace toml_parser * @attention For an array, when no indexing is supplied, the latest entry will be returned. * @remarks If the node to insert exists already, but is marked implicit, the node will be returned and made explicit. In * all other cases the an error will occur that the node already exists. - * @param[in] uiIndex The insertion location to insert the node before. Can be npos or any value larger than the - * collection count to insert the node at the end of the collection. Table array nodes cannot be inserted before value - * nodes or arrays. If the index is referencing a position before a value node or an array, the index is automatically - * corrected. * @param[in] rrangeKeyPath Reference to the token range containing the path to the node to insert. * @param[in] rtArgs Zero or more references to arguments passed to the constructor of the node classes being created by * this function. @@ -1171,7 +1249,7 @@ namespace toml_parser * there the returned node is a table within the table array. */ template - std::shared_ptr Insert(uint32_t uiIndex, const CTokenRange& rrangeKeyPath, const TArgs&... rtArgs); + std::shared_ptr AddNodeFromRange(const CTokenRange& rrangeKeyPath, const TArgs&... rtArgs); /** * @brief Combine the collection with the provided content (mathematical union). @@ -1191,6 +1269,13 @@ namespace toml_parser */ virtual bool Reduce(const std::shared_ptr& rptrCollection) = 0; + /** + * @brief When the parent changes (e.g. when moving items from one parser to the other), the items and all its sub-items + * need to reasign the parser. Overload of CNode::ReassignParser. + * @param[in] rParser Reference to the parser to assign. + */ + virtual void ReassignParser(CParser& rParser) override; + private: /** * @brief When set, the child nodes need grouping (values following each other, tables and table arrays at the end). @@ -1228,8 +1313,6 @@ namespace toml_parser // The child node uses the parent node pointer to indicate which node holds node. // The child node uses the view node pointer to indicate which node displays the node content. - std::vector> m_vecNodeOrder; ///< Vector holding the child nodes (could contain grand children as - ///< well). std::list> m_lstNodes; ///< List holding the direct child nodes. std::list> m_lstRecycleBin; ///< List holding the child elements that were deleted. This will ///< prevent destruction of the node class, which would otherwise @@ -1283,6 +1366,26 @@ namespace toml_parser */ virtual sdv::toml::ENodeType GetType() const override; + /** + * @brief Returns the amount of nodes. Overload of sdv::toml::INodeCollection::GetCount. + * @return The amount of nodes. + */ + virtual uint32_t GetCount() const override; + + /** + * @brief Get the node. Overload of CNodeCollection::Get. + * @param[in] uiIndex Index of the node to get. + * @return Smart pointer to the node object. + */ + virtual std::shared_ptr Get(uint32_t uiIndex) const override; + + /** + * @brief Delete the current node. Overload of sdv::toml::INodeUpdate::DeleteNode. + * @attention A successful deletion will cause all interfaces to the current node to become inoperable. + * @return Returns whether the deletion was successful. + */ + virtual bool DeleteNode() override; + /** * @brief Create the TOML text based on the content using an optional prefix node. Overload of CNode::GenerateTOML. * @param[in] rContext Reference to the context class to use during TOML code generation. @@ -1308,10 +1411,12 @@ namespace toml_parser * table doesn't have a name. * @remarks Additional node composition information will be removed and the order within the parent node might be changed. * @param[in] bInline When set, try to switch to inline. Otherwise try to switch to normal. + * @param[in] bIncludeChildren When set and bInline is not set, applicable child nodes are converted as well (only tables + * and table-arrays can be defined as standard). Making a node inline is always including the children. * @return Returns whether the switch was successful. A switch to the same type (normal to normal or inline to inline is * always successful). When returning false, the switching might not be supported for this type. */ - virtual bool Inline(bool bInline) override; + virtual bool Inline(bool bInline, bool bIncludeChildren = true) override; /** * @brief Checks whether the table was explicitly defined. Overload of CNodeCollection::ExplicitlyDefined. @@ -1342,9 +1447,17 @@ namespace toml_parser */ virtual bool Reduce(const std::shared_ptr& rptrCollection) override; + /** + * @brief After every insert, deletion and shift, the node order of this and all sub- tables need to be rebuild. + * @param[in] bForce Force rebuild, even if locked. + */ + virtual void RebuildNodeOrder(bool bForce) override; + private: - bool m_bDefinedExplicitly = true; ///< When set, the table is defined explicitly. - bool m_bInline = false; ///< Flag determining whether the table is inline or not. + bool m_bDefinedExplicitly = true; ///< When set, the table is defined explicitly. + bool m_bInline = false; ///< Flag determining whether the table is inline or not. + std::vector> m_vecNodeOrder; ///< Vector holding the child nodes (could contain grand children as + ///< well). }; /** @@ -1426,10 +1539,28 @@ namespace toml_parser * @attention Array element access indices starts with 0! * @attention For an array element inserting, when no indexing is supplied, the latest entry will be returned. * @param[in] rssPath Reference to the path of the node to searched for. - * @return Returns a shared pointer to the wanted Node if it was found or a node with invalid content if it was not found. + * @return Returns a shared pointer to the wanted node if it was found or a node with invalid content if it was not found. */ virtual std::shared_ptr Direct(const std::string& rssPath) const override; + /** + * @brief Get or create the parent nodes automotatically from the path. Overload of CNodeCollection::SmartParentCreate. + * @details Elements of tables can be accessed and traversed by using '.' to separated the parent name from child name. + * E.g. 'parent.child' would access the 'child' element of the 'parent' table. Elements of arrays can be accessed and + * traversed by using the index number in brackets. E.g. 'array[3]' would access the fourth element of the array 'array'. + * These access conventions can also be chained like 'table.array[2][1].subtable.integerElement'. + * @attention Array indexing starts with 0! + * @attention For an array, when no indexing or a too large index number is supplied, a new entry will be created and + * returned. + * @param[in] rssPath The path of the node to searched for. + * @param[in] bInsertTableArray Since a table array consists of an array and a table, this is different than the other + * insertions that only insert one element. Some special treatment is needed at certain points. + * @return Returns a pair with the shared pointer to the parent node and the leftover name. If the creation could not be + * done, a NULL pointer is returned. + */ + virtual std::pair, std::string> SmartParentCreate(const std::string& rssPath, + bool bInsertTableArray = false) override; + /** * @brief Create the TOML text based on the content using an optional prefix node. Overload of CNode::GenerateTOML. * @param[in] rContext Reference to the context class to use during TOML code generation. @@ -1450,13 +1581,6 @@ namespace toml_parser */ bool TableArray() const; - /** - * @brief Can the node convert to a standard definition? Overload of sdv::toml::INodeCollectionConvert::CanMakeStandard. - * @return Returns whether the conversion to standard is possible. Returns 'true' when the node is already defined as - * standard node. - */ - virtual bool CanMakeStandard() const override; - /** * @brief The derived class from the node collection can be inline or not. Overload of CNode::Inline. * @return Returns whether the node is an inline node. @@ -1472,10 +1596,19 @@ namespace toml_parser * doesn't have a name that can be used to define the explicit table array. * @remarks Additional node composition information will be removed and the order within the parent node might be changed. * @param[in] bInline When set, try to switch to inline. Otherwise try to switch to normal. + * @param[in] bIncludeChildren When set and bInline is not set, applicable child nodes are converted as well (only tables + * and table-arrays can be defined as standard). Making a node inline is always including the children. * @return Returns whether the switch was successful. A switch to the same type (normal to normal or inline to inline is * always successful). When returning false, the switching might not be supported for this type. */ - virtual bool Inline(bool bInline) override; + virtual bool Inline(bool bInline, bool bIncludeChildren = true) override; + + /** + * @brief Can the node convert to a standard definition? Overload of sdv::toml::INodeCollectionConvert::CanMakeStandard. + * @return Returns whether the conversion to standard is possible. Returns 'true' when the node is already defined as + * standard node. + */ + virtual bool CanMakeStandard() const override; /** * @brief Does the last child node need a comma following the node? @@ -1534,7 +1667,7 @@ namespace toml_parser * @brief Constructor * @param[in] rparser Reference to the TOML parser. */ - CRootTable(CParser& rparser) : CTable(rparser, "root", "", false) + CRootTable(CParser& rparser) : CTable(rparser, "root", "", false, true) {} /** @@ -1550,20 +1683,25 @@ namespace toml_parser */ virtual bool Inline() const override { + // The root node can never be inline. return false; } /** - * @brief With some node collections it is possible to switch between inline and normal. Overload of - * CNodeCollection::Inline. + * @brief Switch between inline and explicit table definition. Overload of CNodeCollection::Inline. + * @attention It is not possible to switch to an explicit table definition if the table is part of an array, since the + * table doesn't have a name. * @remarks Additional node composition information will be removed and the order within the parent node might be changed. * @param[in] bInline When set, try to switch to inline. Otherwise try to switch to normal. + * @param[in] bIncludeChildren When set and bInline is not set, applicable child nodes are converted as well (only tables + * and table-arrays can be defined as standard). Making a node inline is always including the children. * @return Returns whether the switch was successful. A switch to the same type (normal to normal or inline to inline is * always successful). When returning false, the switching might not be supported for this type. */ - virtual bool Inline(bool bInline) override + virtual bool Inline(bool bInline, bool bIncludeChildren = true) override { - return bInline == false; + // Pass call to table implementation. + return CTable::Inline(bInline, bIncludeChildren); } }; @@ -1580,7 +1718,7 @@ namespace toml_parser } template - inline std::shared_ptr CNodeCollection::Insert(uint32_t uiIndex, const CTokenRange& rrangeKeyPath, const TArgs&... rtArgs) + inline std::shared_ptr CNodeCollection::AddNodeFromRange(const CTokenRange& rrangeKeyPath, const TArgs&... rtArgs) { // Get the first part of the node auto prKey = SplitNodeKey(rrangeKeyPath); @@ -1613,9 +1751,8 @@ namespace toml_parser "' exists already, but is not a table array."); // Create the table. - ptrNode = ptrTableArray->Insert(sdv::toml::npos, CTokenRange(prKey.first, prKey.first.get().Next()), + ptrNode = ptrTableArray->AddNodeFromRange(CTokenRange(prKey.first, prKey.first.get().Next()), false, true); - ptrNode->SetViewPtr(Cast()); } else if (!Cast() && itNode != m_lstNodes.end()) { // If existing... this might be a duplicate if not explicitly defined before. @@ -1644,7 +1781,7 @@ namespace toml_parser // If the current node is implicit, take over the inline flag (this determines whether a sub-table definition is // allowed or not). if (!ExplicitlyDefined()) - Inline(ptrNode->Inline()); + Inline(ptrNode->Inline(), false); } } else // Intermediate node @@ -1663,6 +1800,9 @@ namespace toml_parser auto prNextKey = SplitNodeKey(prKey.second); if (prNextKey.first.get().Category() != ETokenCategory::token_integer) { + // The table array should have an ordered vector. This is not the case yet. Rebuild the table array. + ptrNode->Cast()->RebuildNodeOrder(true); + // Get the last table if (!ptrNode->Cast()->GetCount()) throw XTOMLParseException("The parent table array node '" + prKey.first.get().StringValue() + @@ -1700,23 +1840,14 @@ namespace toml_parser std::shared_ptr ptrNodeCollection = ptrNode->Cast(); if (!ptrNodeCollection) throw XTOMLParseException("Parent node is not an array or table '" + ptrNode->GetPath(true) + "'."); - ptrNode = ptrNodeCollection->Insert(sdv::toml::npos, prKey.second, rtArgs...); + ptrNode = ptrNodeCollection->AddNodeFromRange(prKey.second, rtArgs...); if (!ptrNode) throw XTOMLParseException("Could not create the node '" + prKey.first.get().StringValue() + "'."); } - // Insert the node at the requested location if this node is inline or this is the view of the node. - auto itPos = (static_cast(uiIndex) >= m_vecNodeOrder.size()) ? m_vecNodeOrder.end() : - m_vecNodeOrder.begin() + static_cast(uiIndex); - m_vecNodeOrder.insert(itPos, ptrNode); - if (ptrNode->GetParentPtr() != Cast()) - ptrNode->SetViewPtr(Cast()); - // Return the result return ptrNode; } - - } // namespace toml_parser #endif // !defined PARSER_NODE_TOML_H \ No newline at end of file diff --git a/sdv_services/core/toml_parser/parser_toml.cpp b/sdv_services/core/toml_parser/parser_toml.cpp index 226825d..6bf0600 100644 --- a/sdv_services/core/toml_parser/parser_toml.cpp +++ b/sdv_services/core/toml_parser/parser_toml.cpp @@ -54,6 +54,9 @@ namespace toml_parser try { + // Lock the rebuild of the node order + auto lock = CreateRebuildLockObject(); + // Run through all tokens of the lexer and process the tokens. bool bEOF = false; // Explicit test, since the peek could return EOF, but the cursor might not be at the end yet. while (!bEOF && !m_lexer.IsEnd()) @@ -95,7 +98,7 @@ namespace toml_parser } catch (const sdv::toml::XTOMLParseException& e) { - std::cout << e.what() << '\n'; + std::cerr << e.what() << std::endl; throw; } @@ -115,6 +118,11 @@ namespace toml_parser return m_lexer; } + CNodeIndexer& CParser::Indexer() + { + return m_indexer; + } + const CNodeCollection& CParser::Root() const { auto ptrCollection = m_ptrRoot->Cast(); @@ -139,6 +147,49 @@ namespace toml_parser return m_ptrRoot->GenerateTOML(rssPrefixKey); } + CParser::CLockRebuild::CLockRebuild(CParser& rParser) : m_rParser(rParser) + { + rParser.IncrRebuildLockCnt(); + } + + CParser::CLockRebuild::CLockRebuild(const CLockRebuild& rLockRebuild) : m_rParser(rLockRebuild.m_rParser) + { + m_rParser.IncrRebuildLockCnt(); + } + + CParser::CLockRebuild::CLockRebuild(CLockRebuild&& rLockRebuild) : m_rParser(rLockRebuild.m_rParser) + { + m_rParser.IncrRebuildLockCnt(); + } + + CParser::CLockRebuild::~CLockRebuild() + { + m_rParser.DecrRebuildLockCnt(); + } + + CParser::CLockRebuild CParser::CreateRebuildLockObject() + { + return CLockRebuild(*this); + } + + bool CParser::RebuildLocked() const + { + return m_nRebuildLockCnt ? true : false; + } + + void CParser::IncrRebuildLockCnt() + { + ++m_nRebuildLockCnt; + } + + void CParser::DecrRebuildLockCnt() + { + if (!m_nRebuildLockCnt) + return; // Should not occur + --m_nRebuildLockCnt; + if (!m_nRebuildLockCnt) Root().RebuildNodeOrder(false); + } + void CParser::ProcessTable(CNodeTokenRange& rNodeRange) { // Get the table path (table name preceded by parent tables separated with dots). @@ -154,7 +205,7 @@ namespace toml_parser m_lexer.SmartExtendNodeRange(rNodeRange); // Add the table to the root - auto ptrTable = m_ptrRoot->Insert(sdv::toml::npos, rangeKeyPath, false); + auto ptrTable = m_ptrRoot->AddNodeFromRange(rangeKeyPath, false); if (ptrTable) { m_ptrCurrentCollection = ptrTable->Cast(); @@ -176,7 +227,7 @@ namespace toml_parser m_lexer.SmartExtendNodeRange(rNodeRange); // Add the table array to the root - auto ptrTableArray = m_ptrRoot->Insert(sdv::toml::npos, rangeKeyPath); + auto ptrTableArray = m_ptrRoot->AddNodeFromRange(rangeKeyPath); if (ptrTableArray) { m_ptrCurrentCollection = ptrTableArray->Cast(); @@ -226,46 +277,43 @@ namespace toml_parser switch (rAssignmentValue.Category()) { case ETokenCategory::token_boolean: - ptrNode = m_ptrCurrentCollection->Insert(sdv::toml::npos, rrangeKeyPath, rAssignmentValue.BooleanValue(), + ptrNode = m_ptrCurrentCollection->AddNodeFromRange(rrangeKeyPath, rAssignmentValue.BooleanValue(), rAssignmentValue.RawString()); break; case ETokenCategory::token_integer: - ptrNode = m_ptrCurrentCollection->Insert(sdv::toml::npos, rrangeKeyPath, rAssignmentValue.IntegerValue(), + ptrNode = m_ptrCurrentCollection->AddNodeFromRange(rrangeKeyPath, rAssignmentValue.IntegerValue(), rAssignmentValue.RawString()); break; case ETokenCategory::token_float: - ptrNode = m_ptrCurrentCollection->Insert(sdv::toml::npos, rrangeKeyPath, rAssignmentValue.FloatValue(), + ptrNode = m_ptrCurrentCollection->AddNodeFromRange(rrangeKeyPath, rAssignmentValue.FloatValue(), rAssignmentValue.RawString()); break; case ETokenCategory::token_string: switch (rAssignmentValue.StringType()) { case ETokenStringType::literal_string: - ptrNode = m_ptrCurrentCollection->Insert(sdv::toml::npos, rrangeKeyPath, rAssignmentValue.StringValue(), + ptrNode = m_ptrCurrentCollection->AddNodeFromRange(rrangeKeyPath, rAssignmentValue.StringValue(), CStringNode::EQuotationType::literal_string, rAssignmentValue.RawString()); break; case ETokenStringType::multi_line_literal: - ptrNode = m_ptrCurrentCollection->Insert( - sdv::toml::npos, rrangeKeyPath, rAssignmentValue.StringValue(), CStringNode::EQuotationType::multi_line_literal, - rAssignmentValue.RawString()); + ptrNode = m_ptrCurrentCollection->AddNodeFromRange(rrangeKeyPath, rAssignmentValue.StringValue(), + CStringNode::EQuotationType::multi_line_literal, rAssignmentValue.RawString()); break; case ETokenStringType::multi_line_quoted: - ptrNode = m_ptrCurrentCollection->Insert( - sdv::toml::npos, rrangeKeyPath, rAssignmentValue.StringValue(), CStringNode::EQuotationType::multi_line_quoted, - rAssignmentValue.RawString()); + ptrNode = m_ptrCurrentCollection->AddNodeFromRange(rrangeKeyPath, rAssignmentValue.StringValue(), + CStringNode::EQuotationType::multi_line_quoted, rAssignmentValue.RawString()); break; case ETokenStringType::quoted_string: default: - ptrNode = m_ptrCurrentCollection->Insert( - sdv::toml::npos, rrangeKeyPath, rAssignmentValue.StringValue(), CStringNode::EQuotationType::quoted_string, - rAssignmentValue.RawString()); + ptrNode = m_ptrCurrentCollection->AddNodeFromRange(rrangeKeyPath, rAssignmentValue.StringValue(), + CStringNode::EQuotationType::quoted_string, rAssignmentValue.RawString()); break; } break; case ETokenCategory::token_syntax_array_open: { auto ptrCurrentCollectionStored = m_ptrCurrentCollection; - ptrNode = m_ptrCurrentCollection->Insert(sdv::toml::npos, rrangeKeyPath); + ptrNode = m_ptrCurrentCollection->AddNodeFromRange(rrangeKeyPath); m_ptrCurrentCollection = ptrNode->Cast(); m_stackEnvironment.push(EEnvironment::array); ProcessArray(rNodeRange); @@ -276,7 +324,7 @@ namespace toml_parser case ETokenCategory::token_syntax_inline_table_open: { auto ptrCurrentCollectionStored = m_ptrCurrentCollection; - ptrNode = m_ptrCurrentCollection->Insert(sdv::toml::npos, rrangeKeyPath, true); + ptrNode = m_ptrCurrentCollection->AddNodeFromRange(rrangeKeyPath, true); m_ptrCurrentCollection = ptrNode->Cast(); m_stackEnvironment.push(EEnvironment::inline_table); ProcessInlineTable(rNodeRange); diff --git a/sdv_services/core/toml_parser/parser_toml.h b/sdv_services/core/toml_parser/parser_toml.h index a390d66..f953190 100644 --- a/sdv_services/core/toml_parser/parser_toml.h +++ b/sdv_services/core/toml_parser/parser_toml.h @@ -18,6 +18,7 @@ #include "lexer_toml.h" #include "parser_node_toml.h" #include "miscellaneous.h" +#include "parser_node_indexer.h" #include #include #include @@ -25,7 +26,9 @@ /// The TOML parser namespace namespace toml_parser { + // Forward declarations class CNode; + class CNodeCollection; /** * @brief Creates a tree structure from input of UTF-8 encoded TOML source data @@ -33,6 +36,10 @@ namespace toml_parser class CParser : public sdv::IInterfaceAccess, public sdv::toml::ITOMLParser { public: + // Forward declaration + class CLockRebuild; + friend CLockRebuild; ///< Friend class can trigger manage rebuild lock counter. + /** * @brief Construct a new Parser object * @param[in] rssString UTF-8 encoded data of a TOML source @@ -66,6 +73,12 @@ namespace toml_parser */ CLexer& Lexer(); + /** + * @brief Get the indexer object managing the overall order of the nodes. + * @return Reference to the index object. + */ + CNodeIndexer& Indexer(); + /** * @{ * @brief Return the root node. @@ -85,7 +98,65 @@ namespace toml_parser */ std::string GenerateTOML(const std::string& rssPrefixKey = std::string()) const; + /** + * @brief Lock rebuild object preventing rebuilding the node order of all the tables. + */ + class CLockRebuild + { + friend CParser; ///< Parser can access the constructor + + /** + * @brief Constructor + * @param[in] rParser Reference to the parser object + */ + CLockRebuild(CParser& rParser); + + public: + /** + * @brief Copy constructor + * @param[in] rLockRebuild Reference to another rebuild lock object. + */ + CLockRebuild(const CLockRebuild& rLockRebuild); + + /** + * @brief Move constructor + * @param[in] rLockRebuild Reference to another rebuild lock object. + */ + CLockRebuild(CLockRebuild&& rLockRebuild); + + /** + * @brief Destructor + */ + ~CLockRebuild(); + + private: + CParser& m_rParser; ///< Reference to the parser object + }; + + /** + * @brief Create a rebuild lock object. During the lifetime of the object rebuilding the node order is locked using the lock + * counter method. + * @return An instance of the rebuild lock object. + */ + CLockRebuild CreateRebuildLockObject(); + + /** + * @brief Returns whether rebuild is locked at the moment. + * @return Set when rebuild is locked. + */ + bool RebuildLocked() const; + private: + /** + * @brief Increase the rebuild lock counter. A lock count larger than 0 will prevent a rebuild. + */ + void IncrRebuildLockCnt(); + + /** + * @brief Decrease the rebuild lock counter. A lock count of 0 will trigger the rebuild. + */ + void DecrRebuildLockCnt(); + /** * @brief Process a table declaration. * @param[in, out] rNodeRange Reference to the extended token range of the node. @@ -139,10 +210,13 @@ namespace toml_parser inline_table ///< Environment for a table }; - enum_stack m_stackEnvironment; ///< Tracking of environments in nested structures. - std::shared_ptr m_ptrRoot; ///< The one root node. - std::shared_ptr m_ptrCurrentCollection; ///< The current collection node. - CLexer m_lexer; ///< Lexer. + enum_stack m_stackEnvironment; ///< Tracking of environments in nested structures. + std::shared_ptr m_ptrRoot; ///< The one root node. + std::shared_ptr m_ptrCurrentCollection; ///< The current collection node. + CLexer m_lexer; ///< Lexer object user for lexing the TOML code. + CNodeIndexer m_indexer; ///< Indexer managing the oberall order of the nodes. + size_t m_nRebuildLockCnt = 0; ///< A lock counter > 0 will prevent the node order + ///< rebuild. }; } // namespace toml_parser diff --git a/sdv_services/ipc_com/CMakeLists.txt b/sdv_services/ipc_com/CMakeLists.txt index ef48e26..e3e15eb 100644 --- a/sdv_services/ipc_com/CMakeLists.txt +++ b/sdv_services/ipc_com/CMakeLists.txt @@ -25,8 +25,6 @@ add_library(ipc_com SHARED "com_channel.cpp" "marshall_object.h" "marshall_object.cpp" - - #"scheduler.cpp" ) if(UNIX) target_link_libraries(ipc_com rt ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT}) diff --git a/sdv_services/ipc_com/com_channel.cpp b/sdv_services/ipc_com/com_channel.cpp index 4e7d23d..ed436d7 100644 --- a/sdv_services/ipc_com/com_channel.cpp +++ b/sdv_services/ipc_com/com_channel.cpp @@ -18,7 +18,7 @@ #include #include #include -#include "../../global/scheduler/scheduler.cpp" +#include "../../global/scheduler/scheduler.h" CChannelConnector::CChannelConnector(CCommunicationControl& rcontrol, uint32_t uiIndex, sdv::IInterfaceAccess* pChannelEndpoint) : m_rcontrol(rcontrol), m_ptrChannelEndpoint(pChannelEndpoint), diff --git a/sdv_services/ipc_com/com_channel.h b/sdv_services/ipc_com/com_channel.h index b2ce3da..643ded7 100644 --- a/sdv_services/ipc_com/com_channel.h +++ b/sdv_services/ipc_com/com_channel.h @@ -95,8 +95,8 @@ public: * @brief Sends data consisting of multiple data chunks via the IPC connection. * @param[in] tProxyID Marshall ID of the proxy (source). * @param[in] tStubID Marshall ID of the stub (target). - * @param[in] rseqInputData Sequence of data buffers to be sent. May be altered during processing to add/change the sequence content - * without having to copy the data. + * @param[in] rseqInputData Sequence of data buffers to be sent. May be altered during processing to add/change the sequence + * content without having to copy the data. * @return Returns the results of the call or throws a marshall exception. */ sdv::sequence> MakeCall(sdv::ps::TMarshallID tProxyID, sdv::ps::TMarshallID tStubID, @@ -155,7 +155,9 @@ private: sdv::ipc::IDataSend* m_pDataSend = nullptr; ///< Pointer to the send interface. std::mutex m_mtxCalls; ///< Call map protection. std::map m_mapCalls; ///< call map. - CTaskScheduler m_scheduler; ///< Scheduler to process incoming calls. + CTaskScheduler m_scheduler; ///< Scheduler to process incoming calls. + sdv::core::IPermissionControl* m_pPermissionControl = nullptr; ///< Pointer to the permission control interface of the + ///< target system. }; #endif // !defined COM_CHANNEL_H \ No newline at end of file diff --git a/sdv_services/ipc_com/com_ctrl.cpp b/sdv_services/ipc_com/com_ctrl.cpp index 0999351..d10cbb8 100644 --- a/sdv_services/ipc_com/com_ctrl.cpp +++ b/sdv_services/ipc_com/com_ctrl.cpp @@ -16,6 +16,8 @@ #include #include "com_channel.h" #include "marshall_object.h" +#include +#include thread_local CChannelConnector* CCommunicationControl::m_pConnectorContext = nullptr; @@ -40,7 +42,7 @@ void CCommunicationControl::OnShutdown() std::unique_lock lock(m_mtxChannels); auto vecInitialConnectMon = std::move(m_vecInitialConnectMon); lock.unlock(); - for (std::thread& rthread : vecInitialConnectMon) + for (sdv::core::secure_thread& rthread : vecInitialConnectMon) { if (rthread.joinable()) rthread.join(); @@ -63,10 +65,11 @@ sdv::com::TConnectionID CCommunicationControl::CreateServerConnection(/*in*/ sdv switch (eChannelType) { case sdv::com::EChannelType::local_channel: - ssChannelServer = "LocalChannelControl"; + ssChannelServer = static_cast(sdv::app::GetAppSettingsAttribute("Communication.DefaultProvider")); break; case sdv::com::EChannelType::remote_channel: - ssChannelServer = "RemoteChannelControl"; + // Currently not supported + ssChannelServer = "UnknownComProvider"; break; default: return {}; diff --git a/sdv_services/ipc_com/com_ctrl.h b/sdv_services/ipc_com/com_ctrl.h index 7a10887..d9e791e 100644 --- a/sdv_services/ipc_com/com_ctrl.h +++ b/sdv_services/ipc_com/com_ctrl.h @@ -196,7 +196,7 @@ public: private: std::mutex m_mtxChannels; ///< Protect the channel map. std::vector> m_vecChannels; ///< Channel vector. - std::vector m_vecInitialConnectMon; ///< Initial connection monitor. + std::vector m_vecInitialConnectMon; ///< Initial connection monitor. std::recursive_mutex m_mtxObjects; ///< Protect object vectors. std::vector> m_vecMarshallObjects; ///< Vector with marshall objects; lifetime is handled by channel. std::map> m_mapStubObjects; ///< Map of interfaces to stub objects diff --git a/sdv_services/ipc_connect/client.cpp b/sdv_services/ipc_connect/client.cpp index 366f321..8413327 100644 --- a/sdv_services/ipc_connect/client.cpp +++ b/sdv_services/ipc_connect/client.cpp @@ -1,3 +1,4 @@ + /******************************************************************************** * Copyright (c) 2025-2026 ZF Friedrichshafen AG * @@ -18,7 +19,7 @@ #include #include -CRepositoryProxy::CRepositoryProxy(CClient& rClient, sdv::com::TConnectionID tConnection, +CRepositoryProxy::CRepositoryProxy(CClientConnect& rClient, sdv::com::TConnectionID tConnection, sdv::IInterfaceAccess* pRepositoryProxy) : m_rClient(rClient), m_tConnection(tConnection), m_ptrRepositoryProxy(pRepositoryProxy) {} @@ -26,120 +27,131 @@ CRepositoryProxy::CRepositoryProxy(CClient& rClient, sdv::com::TConnectionID tCo void CRepositoryProxy::DestroyObject() { // Call the client to disconnect the connection and destroy the object. - m_rClient.Disconnect(m_tConnection); + m_rClient.Disconnect(); } -bool CClient::OnInitialize() +sdv::com::TConnectionID CRepositoryProxy::GetConnectionID() const { - return true; + return m_tConnection; } -void CClient::OnShutdown() -{ - sdv::com::IConnectionControl* pConnectionControl = sdv::core::GetObject("CommunicationControl"); - if (!pConnectionControl) - SDV_LOG_ERROR("Failed to get communication control!"); - - // Disconnect from all repositories - std::unique_lock lock(m_mtxRepositoryProxies); - auto mapRepositoryProxiesCopy = std::move(m_mapRepositoryProxies); - lock.unlock(); - if (pConnectionControl) - { - for (const auto& rvtRepository : mapRepositoryProxiesCopy) - pConnectionControl->RemoveConnection(rvtRepository.first); - } -} - -sdv::IInterfaceAccess* CClient::Connect(const sdv::u8string& ssConnectString) +bool CClientConnect::OnInitialize() { const sdv::app::IAppContext* pContext = sdv::core::GetCore(); if (!pContext) { SDV_LOG_ERROR("Failed to get application context!"); - return nullptr; + return false; } sdv::com::IConnectionControl* pConnectionControl = sdv::core::GetObject("CommunicationControl"); if (!pConnectionControl) { SDV_LOG_ERROR("Failed to get communication control!"); - return nullptr; + return false; } - sdv::ipc::IChannelAccess* pChannelAccess = nullptr; - std::string ssConfig; - try + // Check for a provider. Without provider there is no object to use for listening. + if (m_ssProvider.empty()) { - // Determine whether the service is running as server or as client. - sdv::toml::CTOMLParser config(ssConnectString); - std::string ssType = config.GetDirect("Client.Type").GetValue(); - if (ssType.empty()) ssType = "Local"; - if (ssType == "Local") - { - uint32_t uiInstanceID = config.GetDirect("Client.Instance").GetValue(); - pChannelAccess = sdv::core::GetObject("LocalChannelControl"); - if (!pChannelAccess) - { - SDV_LOG_ERROR("No local channel control or channel control not configured as client!"); - return nullptr; - } - - ssConfig = std::string(R"code([IpcChannel] -Name = "LISTENER_)code") + std::to_string(uiInstanceID ? uiInstanceID : pContext->GetInstanceID()) + R"code(" -)code"; - - } - else if (ssType == "Remote") - { - std::string ssInterface = config.GetDirect("Client.Interface").GetValue(); - uint32_t uiPort = config.GetDirect("Client.Interface").GetValue(); - if (ssInterface.empty() || !uiPort) - { - SDV_LOG_ERROR("Missing interface or port number to initialize a remote client!"); - return nullptr; - } - pChannelAccess = sdv::core::GetObject("RemoteChannelControl"); - if (!pChannelAccess) - { - SDV_LOG_ERROR("No remote channel control or channel control not configured as client!"); - return nullptr; - } - - ssConfig = R"code([IpcChannel] -Interface = ")code" + ssInterface + R"code( -Port = ")code" + std::to_string(uiPort) + R"code( -)code"; - } - else - { - SDV_LOG_ERROR("Invalid or missing listener configuration for listener service!"); - SetObjectIntoConfigErrorState(); - return nullptr; - } + SDV_LOG_ERROR("Missing provider name for creating a client object!"); + return false; } - catch (const sdv::toml::XTOMLParseException& rexcept) + sdv::ipc::IChannelAccess* pChannelAccess = sdv::core::GetObject(m_ssProvider); + if (!pChannelAccess) { - SDV_LOG_ERROR("Invalid service configuration for listener service: ", rexcept.what(), "!"); - SetObjectIntoConfigErrorState(); - return nullptr; + SDV_LOG_ERROR("Cannot instantiate provider '", m_ssProvider, "' for the creation of a client object!"); + return false; } + // The connection will be established in the Connect function. + return true; +} + +void CClientConnect::OnShutdown() +{ + sdv::com::IConnectionControl* pConnectionControl = sdv::core::GetObject("CommunicationControl"); + if (!pConnectionControl) + SDV_LOG_ERROR("Failed to get communication control!"); + + // Disconnect + Disconnect(); +} + +bool CClientConnect::Connect() +{ + sdv::u8string ssProvider; + sdv::u8string ssConfig; + + { + std::unique_lock lock(m_mtx); + ssProvider = m_ssProvider; + ssConfig = BuildObjectConfig(); + } + + const sdv::app::IAppContext* pContext = sdv::core::GetCore(); + if (!pContext) + { + SDV_LOG_ERROR("Failed to get application context!"); + return false; + } + sdv::com::IConnectionControl* pConnectionControl = sdv::core::GetObject("CommunicationControl"); + if (!pConnectionControl) + { + SDV_LOG_ERROR("Failed to get communication control!"); + return false; + } + + // Check for a provider. Without provider there is no object to use for listening. + if (ssProvider.empty()) + { + SDV_LOG_ERROR("Missing provider name for creating a client object!"); + return false; + } + + sdv::ipc::IChannelAccess* pChannelAccess = sdv::core::GetObject(ssProvider); + if (!pChannelAccess) + { + SDV_LOG_ERROR("Cannot instantiate provider '", m_ssProvider, "' for the creation of a client object!"); + return false; + } + + // Get the repository + sdv::TInterfaceAccessPtr ptrRespository = sdv::core::GetObject("RepositoryService"); + if (!ptrRespository) + { + SDV_LOG_ERROR("Failed to get repository service!"); + return false; + } + + // Add a dependency of the provider to this service (to prevent the provider to be terminated while the service is still + // running). + sdv::core::IObjectDependency* pObjectDependency = ptrRespository.GetInterface(); + if (!pObjectDependency) + { + SDV_LOG_ERROR("Failed to get the object dependency interface!"); + return false; + } + + pObjectDependency->AddObjectDependency(Self().ssName, ssProvider); + + // First access the listener channel. This allows us to access the channel creation interface. // TODO: Use named mutex to prevent multiple connections at the same time. // Connect to the channel. sdv::TObjectPtr ptrListenerEndpoint = pChannelAccess->Access(ssConfig); + // Assign the endpoint to the communication service. sdv::IInterfaceAccess* pListenerProxy = nullptr; - sdv::com::TConnectionID tListenerConnection = pConnectionControl->AssignClientEndpoint(ptrListenerEndpoint, 5000, - pListenerProxy); + sdv::com::TConnectionID tListenerConnection = + pConnectionControl->AssignClientEndpoint(ptrListenerEndpoint, 5000, pListenerProxy); ptrListenerEndpoint.Clear(); // Lifetime has been taken over by communication control. if (!tListenerConnection || !pListenerProxy) { SDV_LOG_ERROR("Could not assign the client endpoint!"); if (tListenerConnection != sdv::com::TConnectionID{}) pConnectionControl->RemoveConnection(tListenerConnection); - return nullptr; + return false; } sdv::TInterfaceAccessPtr ptrListenerProxy(pListenerProxy); @@ -149,9 +161,26 @@ Port = ")code" + std::to_string(uiPort) + R"code( { SDV_LOG_ERROR("Could not get the channel creation interface!"); if (tListenerConnection != sdv::com::TConnectionID{}) pConnectionControl->RemoveConnection(tListenerConnection); - return nullptr; + return false; } - sdv::u8string ssConnectionString = pRequestChannel->RequestChannel(""); + + sdv::u8string ssRequestConfig; + + // Tunnel providers need the full object config so the private channel + // can preserve provider-specific fields such as the tunnel name. + // For shared memory we keep the legacy behavior and let the provider + // create its own private channel details. + if (ssProvider == "unix_domain_sockets_tunnel") + { + ssRequestConfig = ssConfig; + } + else + { + ssRequestConfig.clear(); + } + + sdv::u8string ssConnectionString = pRequestChannel->RequestChannel(ssRequestConfig); + // Disconnect from the listener if (tListenerConnection != sdv::com::TConnectionID{}) pConnectionControl->RemoveConnection(tListenerConnection); @@ -159,7 +188,7 @@ Port = ")code" + std::to_string(uiPort) + R"code( if (ssConnectionString.empty()) { SDV_LOG_ERROR("Could not get the private channel connection information!"); - return nullptr; + return false; } // TODO: Use named mutex to prevent multiple connections at the same time. @@ -174,30 +203,64 @@ Port = ")code" + std::to_string(uiPort) + R"code( { SDV_LOG_ERROR("Could not assign the client endpoint to the private channel!"); if (tPrivateConnection != sdv::com::TConnectionID{}) pConnectionControl->RemoveConnection(tPrivateConnection); - return nullptr; + return false; } // Create a remote repository object - std::unique_lock lock(m_mtxRepositoryProxies); - m_mapRepositoryProxies.try_emplace(tPrivateConnection, *this, tPrivateConnection, pPrivateProxy); + m_ptrRemoteRepo = std::make_shared(*this, tPrivateConnection, pPrivateProxy); - return pPrivateProxy; + return true; } -void CClient::Disconnect(sdv::com::TConnectionID tConnectionID) +bool CClientConnect::Disconnect() { - // Find the connection, disconnect and remove the connection from the repository list. - std::unique_lock lock(m_mtxRepositoryProxies); - auto itRepository = m_mapRepositoryProxies.find(tConnectionID); - if (itRepository == m_mapRepositoryProxies.end()) return; + std::unique_lock lock(m_mtx); + + if (!m_ptrRemoteRepo) return false; + + // Whatever happens, the connection will be removed + std::shared_ptr ptrRemoteRepoLocal = std::move(m_ptrRemoteRepo); // Disconnect sdv::com::IConnectionControl* pConnectionControl = sdv::core::GetObject("CommunicationControl"); if (!pConnectionControl) + { SDV_LOG_ERROR("Failed to get communication control!"); - else - pConnectionControl->RemoveConnection(itRepository->first); + return false; + } + pConnectionControl->RemoveConnection(ptrRemoteRepoLocal->GetConnectionID()); - // Remove entry - m_mapRepositoryProxies.erase(itRepository); + // Get the repository + sdv::TInterfaceAccessPtr ptrRespository = sdv::core::GetObject("RepositoryService"); + if (!ptrRespository) + { + SDV_LOG_ERROR("Failed to get repository service!"); + return false; + } + + // Add a dependency of the provider to this service (to prevent the provider to be terminated while the service is still + // running). + sdv::core::IObjectDependency* pObjectDependency = ptrRespository.GetInterface(); + if (!pObjectDependency) + { + SDV_LOG_ERROR("Failed to get the object dependency interface!"); + return false; + } + pObjectDependency->RemoveObjectDependency("ClientConnectService", m_ssProvider); + + return true; +} + +bool CClientConnect::IsConnected() const +{ + std::unique_lock lock(m_mtx); + + return m_ptrRemoteRepo ? true : false; +} + +sdv::IInterfaceAccess* CClientConnect::GetRemoteRepository() +{ + std::unique_lock lock(m_mtx); + + return m_ptrRemoteRepo ? m_ptrRemoteRepo.get() : nullptr; } diff --git a/sdv_services/ipc_connect/client.h b/sdv_services/ipc_connect/client.h index 7b50edc..3811177 100644 --- a/sdv_services/ipc_connect/client.h +++ b/sdv_services/ipc_connect/client.h @@ -19,7 +19,7 @@ #include // Forward declaration. -class CClient; +class CClientConnect; /** * @brief Class managing the connection and providing access to the server repository through a proxy. @@ -33,7 +33,7 @@ public: * @param[in] tConnection The connection ID to the server. * @param[in] pRepositoryProxy Proxy to the server repository. */ - CRepositoryProxy(CClient& rClient, sdv::com::TConnectionID tConnection, sdv::IInterfaceAccess* pRepositoryProxy); + CRepositoryProxy(CClientConnect& rClient, sdv::com::TConnectionID tConnection, sdv::IInterfaceAccess* pRepositoryProxy); /** * @brief Do not allow a copy constructor. @@ -59,8 +59,14 @@ public: */ virtual void DestroyObject() override; + /** + * @brief Get the connection ID for this connection. + * @return The connection ID. + */ + sdv::com::TConnectionID GetConnectionID() const; + private: - CClient& m_rClient; ///< Reference to the client object. + CClientConnect& m_rClient; ///< Reference to the client object. sdv::com::TConnectionID m_tConnection = {}; ///< Connection ID. sdv::TInterfaceAccessPtr m_ptrRepositoryProxy; ///< Smart pointer to the remote repository. }; @@ -68,7 +74,7 @@ private: /** * @brief Client object */ -class CClient : public sdv::CSdvObject, public sdv::com::IClientConnect +class CClientConnect : public sdv::CSdvObject, public sdv::com::IClientConnect { public: // Interface map @@ -78,8 +84,15 @@ public: // Object declaration DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::system_object) - DECLARE_OBJECT_CLASS_NAME("ConnectionService") - DECLARE_OBJECT_SINGLETON() + DECLARE_OBJECT_CLASS_NAME("ClientConnectService") + DECLARE_OBJECT_DEPENDENCIES("CommunicationControl") + + // Parameter map + BEGIN_SDV_PARAM_MAP() + SDV_PARAM_ENABLE_LOCKING() + SDV_PARAM_GROUP("Provider") + SDV_PARAM_ENTRY(m_ssProvider, "Name", "", "", "Provider name to create a connection for.") + END_SDV_PARAM_MAP() /** * @brief Initialization event, called after object configuration was loaded. Overload of sdv::CSdvObject::OnInitialize. @@ -93,40 +106,40 @@ public: virtual void OnShutdown() override; /** - * @brief Connect to a remote system using the connection string to contact the system. Overload of - * sdv::com::IClientConnect::Connect. - * @remarks After a successful connection, the ConnectClient utility is not needed any more. - * @param[in] ssConnectString Optional connection string to use for connection. If not provided, the connection will - * automatically get the connection ID from the app-control service (default). The connection string for a local - * connection can be of the form: - * @code - * [Client] - * Type = "Local" - * Instance = 1234 # Optional: only use when connecting to a system with a different instance ID. - * @endcode - * And the following can be used for a remote connection: - * @code - * [Client] - * Type = "Remote" - * Interface = "127.0.0.1" - * Port = 2000 - * @endcode - * @return Returns an interface to the repository of the remote system or a NULL pointer if not found. + * @brief Connect to a remote system. Overload of IClientConnect::Connect. + * @return Returns whether connect was successful. */ - virtual sdv::IInterfaceAccess* Connect(const sdv::u8string& ssConnectString) override; + virtual bool Connect() override; /** - * @brief Disconnect and remove the remote repository object. - * @param[in] tConnectionID The ID of the connection. + * @brief Disconnect from a connected system. Overload of IClientConnect::Disconnect. + * @return Returns whether disconnect was successful. */ - void Disconnect(sdv::com::TConnectionID tConnectionID); + bool Disconnect() override; + + /** + * @brief State of the current connection. Overload of IClientConnect::IsConnected. + * @return Returns whether an active connection exists. + * @return The connect state. + */ + bool IsConnected() const override; + + /** + * @brief Get the remote repository that is available after connection. Overload of IClientConnect::GetRemoteRepository. + * @remarks For main, isolated and external applications, the remote repository will be automatically linked to the + * local repository. Hence a requests for the repository is not needed. For all other applications, access must be + * explicitly acquired through this interface. + * @return Interface to the remote repository if a successful connection is established. The interface is valid until + * disconnect is called or the client connection service is terminated. + */ + sdv::IInterfaceAccess* GetRemoteRepository() override; private: - std::mutex m_mtxRepositoryProxies; ///< Protect access to the remnote repository map. - std::map m_mapRepositoryProxies; ///< map of remote repositories. + mutable std::mutex m_mtx; ///< Protect against multiple parallel connection activities. + sdv::u8string m_ssProvider; ///< Name of the provider to use for the listening. + std::shared_ptr m_ptrRemoteRepo; ///< Interface to the remote repository. }; -DEFINE_SDV_OBJECT(CClient) - +DEFINE_SDV_OBJECT(CClientConnect) #endif // !defined CLIENT_H \ No newline at end of file diff --git a/sdv_services/ipc_connect/listener.cpp b/sdv_services/ipc_connect/listener.cpp index ba3f582..911b385 100644 --- a/sdv_services/ipc_connect/listener.cpp +++ b/sdv_services/ipc_connect/listener.cpp @@ -16,11 +16,12 @@ #include #include #include +#include CChannelBroker::CChannelBroker(CListener& rListener) : m_rListener(rListener) {} -sdv::u8string CChannelBroker::RequestChannel(/*in*/ const sdv::u8string& /*ssConfig*/) +sdv::u8string CChannelBroker::RequestChannel(/*in*/ const sdv::u8string& ssConfig) { // Get the communication control sdv::com::IConnectionControl* pConnectionControl = sdv::core::GetObject("CommunicationControl"); @@ -39,26 +40,30 @@ sdv::u8string CChannelBroker::RequestChannel(/*in*/ const sdv::u8string& /*ssCon } // Get the channel control. - sdv::ipc::ICreateEndpoint* pEndpoint = nullptr; - if (m_rListener.IsLocalListener()) - pEndpoint = sdv::core::GetObject("LocalChannelControl"); - else - pEndpoint = sdv::core::GetObject("RemoteChannelControl"); + sdv::ipc::ICreateEndpoint* pEndpoint = sdv::core::GetObject(m_rListener.GetProviderName()); if (!pEndpoint) { SDV_LOG_ERROR("No local channel control!"); return {}; } - // Create the endpoint - sdv::ipc::SChannelEndpoint sEndpoint = pEndpoint->CreateEndpoint(sdv::u8string()); + // Forward protocol-specific configuration to the provider when creating the private channel. + SDV_LOG_INFO("[IPC_CONNECT][Listener] RequestChannel input config:\n", ssConfig); + + sdv::ipc::SChannelEndpoint sEndpoint = pEndpoint->CreateEndpoint(ssConfig); if (!sEndpoint.pConnection) { SDV_LOG_ERROR("Could not create the endpoint for channel request!"); - return sdv::u8string(); + return {}; } + + SDV_LOG_INFO("[IPC_CONNECT][Listener] RequestChannel produced connect string: ", sEndpoint.ssConnectString); + sdv::TObjectPtr ptrEndpoint(sEndpoint.pConnection); // Does automatic destruction if failure happens. + // Restrict access permissions + sdv::core::CAccessPermission permission = sdv::core::RestrictAccessPermission(sdv::core::EAccessPermission::local_access); + // Assign the endpoint to the communication service. sdv::com::TConnectionID tConnection = pConnectionControl->AssignServerEndpoint(ptrEndpoint, ptrRespository, 100, false); ptrEndpoint.Clear(); // Lifetime taken over by communication control. @@ -93,49 +98,21 @@ bool CListener::OnInitialize() return false; } - sdv::ipc::ICreateEndpoint* pEndpoint = nullptr; - std::string ssConfig; - if (m_ssType == "Local") + // Check for a provider. Without provider there is no object to use for listening. + if (m_ssProvider.empty()) { - m_bLocalListener = true; - pEndpoint = sdv::core::GetObject("LocalChannelControl"); - if (!pEndpoint) - { - SDV_LOG_ERROR("No local channel control!"); - return false; - } - - // Request the instance ID from the app control - ssConfig = std::string(R"code([IpcChannel] -Name = "LISTENER_)code") + std::to_string(m_uiInstanceID ? m_uiInstanceID : pContext->GetInstanceID()) + R"code(" -Size = 2048 -)code"; - } - else if (m_ssType == "Remote") - { - m_bLocalListener = false; - if (m_ssInterface.empty() || !m_uiPort) - { - SDV_LOG_ERROR("Missing interface or port number to initialize a remote listener!"); - return false; - } - pEndpoint = sdv::core::GetObject("RemoteChannelControl"); - if (!pEndpoint) - { - SDV_LOG_ERROR("No remote channel control!"); - return false; - } - - ssConfig = R"code([IpcChannel] -Interface = ")code" + m_ssInterface + R"code( -Port = ")code" + std::to_string(m_uiPort) + R"code( -)code"; - } - else - { - SDV_LOG_ERROR("Invalid or missing listener configuration for listener service!"); + SDV_LOG_ERROR("Missing provider name for creating a listener object!"); return false; } + sdv::ipc::ICreateEndpoint* pEndpoint = sdv::core::GetObject(m_ssProvider); + if (!pEndpoint) + { + SDV_LOG_ERROR("Cannot instantiate provider '", m_ssProvider, "' for the creation of a listener object!"); + return false; + } + + // Get the IpcChannel information from the the configuration + auto ssConfig = BuildObjectConfig(); // Create the endpoint sdv::ipc::SChannelEndpoint sEndpoint = pEndpoint->CreateEndpoint(ssConfig); @@ -146,6 +123,24 @@ Port = ")code" + std::to_string(m_uiPort) + R"code( } sdv::TObjectPtr ptrEndpoint(sEndpoint.pConnection); // Does automatic destruction if failure happens. + // Get the repository + sdv::TInterfaceAccessPtr ptrRespository = sdv::core::GetObject("RepositoryService"); + if (!ptrRespository) + { + SDV_LOG_ERROR("Failed to get repository service!"); + return false; + } + + // Add a dependency of the provider to this service (to prevent the provider to be terminated while the service is still + // running). + sdv::core::IObjectDependency* pObjectDependency = ptrRespository.GetInterface(); + if (!pObjectDependency) + { + SDV_LOG_ERROR("Failed to get the object dependency interface!"); + return false; + } + pObjectDependency->AddObjectDependency(Self().ssName, m_ssProvider); + // Assign the endpoint to the communication service. m_tConnection = pConnectionControl->AssignServerEndpoint(ptrEndpoint, &m_broker, 100, true); ptrEndpoint.Clear(); // Lifetime taken over by communication control. @@ -173,9 +168,8 @@ void CListener::OnShutdown() m_ptrConnection.Clear(); } -bool CListener::IsLocalListener() const + +const sdv::u8string& CListener::GetProviderName() { - return m_bLocalListener; + return m_ssProvider; } - - diff --git a/sdv_services/ipc_connect/listener.h b/sdv_services/ipc_connect/listener.h index 1e36b7a..38df1ee 100644 --- a/sdv_services/ipc_connect/listener.h +++ b/sdv_services/ipc_connect/listener.h @@ -52,6 +52,25 @@ private: /** * @brief Listener object + * @details the lister is instantiated using the following parameter information + * @code + * # Provider to use for listening + * [Provider] + * Name = "" + * + * # Additional channel information for the listener (needed for unique listener identification) + * [IpcChannel] + * xyz = "" + * @endcode + * + * For example for shared memory: + * @code + * [Provider] + * Name = "DefaultSharedMemory" + * [IpcChannel] + * Name = "LISTENER_1234" + * Size = 10240 + * @endcode */ class CListener : public sdv::CSdvObject { @@ -63,34 +82,18 @@ public: // Object declaration DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::system_object) - DECLARE_OBJECT_CLASS_NAME("ConnectionListenerService") + DECLARE_OBJECT_CLASS_NAME("ListenerConnectService") + DECLARE_OBJECT_DEPENDENCIES("CommunicationControl") // Parameter map BEGIN_SDV_PARAM_MAP() SDV_PARAM_ENABLE_LOCKING() - SDV_PARAM_GROUP("Listener") - SDV_PARAM_ENTRY(m_ssType, "Type", "Local", "", "The type of listener \"Local\" or \"Remote\".") - SDV_PARAM_ENTRY(m_uiInstanceID, "Instance", 0, "", "The instance ID to listen for.") - SDV_PARAM_ENTRY(m_ssInterface, "Interface", "", "", "Interface identification.") - SDV_PARAM_ENTRY(m_uiPort, "Port", 0, "", "Port number for connection.") + SDV_PARAM_GROUP("Provider") + SDV_PARAM_ENTRY(m_ssProvider, "Name", "", "", "Provider name to create a listener for.") END_SDV_PARAM_MAP() /** * @brief Initialization event, called after object configuration was loaded. Overload of sdv::CSdvObject::OnInitialize. - * @details The object configuration contains the information needed to start the listener. The following configuration is - * available for the local listener: - * @code - * [Listener] - * Type = "Local" - * Instance = 1000 # Normally not used; system instance ID is used automatically. - * @endcode - * And the following is available for a remote listener: - * @code - * [Listener] - * Type = "Remote" - * Interface = "127.0.0.1" - * Port = 2000 - * @endcode * @return Returns 'true' when the initialization was successful, 'false' when not. */ virtual bool OnInitialize() override; @@ -101,19 +104,15 @@ public: virtual void OnShutdown() override; /** - * @brief When set, the listener is configured to be a local listener. Otherwise the listerner is configured as remote listener. - * @return Boolean set when local lostener. + * @brief Get the provider name used by this listener. + * @return Reference to the provider name. */ - bool IsLocalListener() const; + const sdv::u8string& GetProviderName(); private: - sdv::u8string m_ssType; ///< Listener type: "Local" or "Remote" - uint32_t m_uiInstanceID = 0; ///< Instance ID to listen for. - std::string m_ssInterface; ///< Interface string for remote listener. - uint32_t m_uiPort = 0; ///< Port for remote listener. + sdv::u8string m_ssProvider; ///< Name of the provider to use for the listening. sdv::TObjectPtr m_ptrConnection; ///< The connection object. CChannelBroker m_broker; ///< Channel broker, used to request new channels - bool m_bLocalListener = true; ///< When set, the listener is a local listener; otherwise a remote listener. sdv::com::TConnectionID m_tConnection = {}; ///< Channel connection ID. }; diff --git a/sdv_services/ipc_shared_mem/channel_mgnt.cpp b/sdv_services/ipc_shared_mem/channel_mgnt.cpp index 9506e10..fffe76c 100644 --- a/sdv_services/ipc_shared_mem/channel_mgnt.cpp +++ b/sdv_services/ipc_shared_mem/channel_mgnt.cpp @@ -56,15 +56,15 @@ sdv::IInterfaceAccess* CSharedMemChannelMgnt::Access(const sdv::u8string& ssConn sdv::toml::CTOMLParser parser(ssConnectString); if (!parser.IsValid()) return nullptr; - // Is this a configuration provided by the endpoint (uses a "Provider" key), then this is a connection string. Use this + // Is this a configuration provided by the endpoint (uses a "ConnectParam" key), then this is a connection string. Use this // to connect to the shared memory. std::shared_ptr ptrConnection; - if (parser.GetDirect("Provider").IsValid()) + if (parser.GetDirect("ConnectParam").IsValid()) ptrConnection = std::make_shared(m_watchdog, ssConnectString.c_str()); else { std::string ssName = static_cast(parser.GetDirect("IpcChannel.Name").GetValue()); - ptrConnection = std::make_shared(m_watchdog, 0,ssName, false); + ptrConnection = std::make_shared(m_watchdog, 0, ssName, false); } if (!ptrConnection) return {}; m_watchdog.AddConnection(ptrConnection); diff --git a/sdv_services/ipc_shared_mem/channel_mgnt.h b/sdv_services/ipc_shared_mem/channel_mgnt.h index 6784152..2c9f2d1 100644 --- a/sdv_services/ipc_shared_mem/channel_mgnt.h +++ b/sdv_services/ipc_shared_mem/channel_mgnt.h @@ -43,9 +43,7 @@ public: // Object declarations DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::system_object) - DECLARE_OBJECT_CLASS_NAME("DefaultSharedMemoryChannelControl") - DECLARE_OBJECT_CLASS_ALIAS("LocalChannelControl") - DECLARE_DEFAULT_OBJECT_NAME("LocalChannelControl") + DECLARE_OBJECT_CLASS_NAME("DefaultSharedMemory") DECLARE_OBJECT_SINGLETON() /** diff --git a/sdv_services/ipc_shared_mem/connection.cpp b/sdv_services/ipc_shared_mem/connection.cpp index 9b7afc9..0b0b334 100644 --- a/sdv_services/ipc_shared_mem/connection.cpp +++ b/sdv_services/ipc_shared_mem/connection.cpp @@ -117,7 +117,7 @@ std::string CConnection::GetConnectionString() { // The connection string is of the form: // [Provider] - // Name = "DefaultSharedMemoryChannelControl" + // Name = "DefaultSharedMemory" // // [[ConnectParam]] // Type = "shared_mem" @@ -132,9 +132,9 @@ std::string CConnection::GetConnectionString() // SyncTx = "SDV_TX_SYNC_REQUEST_1234" // SyncRx = "SDV_RX_SYNC_REQUEST_1234" // Direction = "request" - std::string ssConnectionString = R"code([Provider] -Name = "LocalChannelControl" -)code" + std::string("\n") + m_sender.GetConnectionString() + "\n" + m_receiver.GetConnectionString(); + std::string ssConnectionString = R"toml([Provider] +Name = "DefaultSharedMemory" +)toml" + std::string("\n") + m_sender.GetConnectionString() + "\n" + m_receiver.GetConnectionString(); return ssConnectionString; } @@ -352,7 +352,7 @@ bool CConnection::AsyncConnect(sdv::IInterfaceAccess* pReceiver) std::unique_lock lock(m_mtxConnect); // Allowed to connect? - if (m_eConnectState != sdv::ipc::EConnectState::uninitialized) + if (m_eConnectState != sdv::ipc::EConnectState::uninitialized && m_eConnectState != sdv::ipc::EConnectState::disconnected) { for (auto& rprEventCallback : m_lstEventCallbacks) if (rprEventCallback.pCallback && rprEventCallback.uiCookie) @@ -377,9 +377,9 @@ bool CConnection::AsyncConnect(sdv::IInterfaceAccess* pReceiver) SetConnectState(sdv::ipc::EConnectState::initialized); // Start the receiving thread (wait until started). - m_threadReceive = std::thread(&CConnection::ReceiveMessages, this); + m_threadReceive = sdv::core::secure_thread(&CConnection::ReceiveMessages, this); #if ENABLE_DECOUPLING > 0 - m_threadDecoupleReceive = std::thread(&CConnection::DecoupleReceive, this); + m_threadDecoupleReceive = sdv::core::secure_thread(&CConnection::DecoupleReceive, this); #endif if (!m_bStarted) m_cvStartConnect.wait_for(lock, std::chrono::milliseconds(1000)); @@ -602,9 +602,10 @@ void CConnection::ReceiveMessages() auto optPacket = m_receiver.TryRead(); if (!optPacket) { + // TODO EVE: Also allow synchronization take place when disconnected. // Start communication, but only if connection is client based. Server based should not start the communication. If // there is no client, the server would otherwise fill its send-buffer. Repeat sending every 500ms. - if (!m_bServer && (/*m_eConnectState == sdv::ipc::EConnectState::disconnected ||*/ m_eConnectState == sdv::ipc::EConnectState::initialized)) + if (!m_bServer && (m_eConnectState == sdv::ipc::EConnectState::disconnected || m_eConnectState == sdv::ipc::EConnectState::initialized)) { // Send request auto tpNow = std::chrono::high_resolution_clock::now(); @@ -1001,9 +1002,10 @@ void CConnection::ReceiveConnectTerm(CMessage& /*rMessage*/) m_sender.CancelSend(); m_sender.ResetRx(); - // Send sync request (do not wait until next round in case a very short connection <100ms took place). - if (m_bServer) - Send(SMsgHdr{ SDVFrameworkInterfaceVersion, EMsgType::sync_request }); + // TODO EVE: Disabled, since state of client straight goes into "connecting" after being disconnected. + //// Send sync request (do not wait until next round in case a very short connection <100ms took place). + //if (m_bServer) + // Send(SMsgHdr{ SDVFrameworkInterfaceVersion, EMsgType::sync_request }); } void CConnection::ReceiveDataMessage(CMessage& rMessage, SDataContext& rsDataCtxt) diff --git a/sdv_services/ipc_shared_mem/connection.h b/sdv_services/ipc_shared_mem/connection.h index 037dc7e..88eac04 100644 --- a/sdv_services/ipc_shared_mem/connection.h +++ b/sdv_services/ipc_shared_mem/connection.h @@ -257,7 +257,7 @@ private: sdv::CLifetimeCookie m_cookie = sdv::CreateLifetimeCookie(); ///< Lifetime cookie to manage module lifetime. CSharedMemBufferTx m_sender; ///< Shared buffer for sending. CSharedMemBufferRx m_receiver; ///< Shared buffer for receiving. - std::thread m_threadReceive; ///< Thread which receives data from the socket. + sdv::core::secure_thread m_threadReceive; ///< Thread which receives data from the socket. std::atomic m_eConnectState = sdv::ipc::EConnectState::uninitialized; ///< the state of the connection sdv::ipc::IDataReceiveCallback* m_pReceiver = nullptr; ///< Receiver to pass the messages to if available std::shared_mutex m_mtxEventCallbacks; ///< Protect access to callback list. Only locking when @@ -275,7 +275,7 @@ private: #if ENABLE_DECOUPLING > 0 std::mutex m_mtxReceive; ///< Protect receive queue. std::queue>> m_queueReceive; ///< Receive queue to decouple receiving and processing. - std::thread m_threadDecoupleReceive; ///< Decoupled receive thread. + sdv::core::secure_thread m_threadDecoupleReceive; ///< Decoupled receive thread. std::condition_variable m_cvReceiveAvailable; ///< Condition variable synchronizing the processing. std::condition_variable m_cvReceiveProcessed; ///< Condition variable synchronizing the processing. #endif diff --git a/sdv_services/ipc_shared_mem/watchdog.cpp b/sdv_services/ipc_shared_mem/watchdog.cpp index b0ba781..9bc2354 100644 --- a/sdv_services/ipc_shared_mem/watchdog.cpp +++ b/sdv_services/ipc_shared_mem/watchdog.cpp @@ -22,7 +22,7 @@ CWatchDog::CWatchDog() { - m_threadScheduledConnectionDestructions = std::thread(&CWatchDog::HandleScheduledConnectionDestructions, this); + m_threadScheduledConnectionDestructions = sdv::core::secure_thread(&CWatchDog::HandleScheduledConnectionDestructions, this); } CWatchDog::~CWatchDog() diff --git a/sdv_services/ipc_shared_mem/watchdog.h b/sdv_services/ipc_shared_mem/watchdog.h index 73f1af0..90668e3 100644 --- a/sdv_services/ipc_shared_mem/watchdog.h +++ b/sdv_services/ipc_shared_mem/watchdog.h @@ -34,6 +34,7 @@ #include #include +#include #include #include #include @@ -127,7 +128,7 @@ private: std::condition_variable m_cvTriggerConnectionDestruction; ///< Condition variable used to trigger when a ///< connection is scheduled for destruction. std::queue> m_queueScheduledConnectionDestructions; ///< Scheduled connection for destruction. - std::thread m_threadScheduledConnectionDestructions; ///< Thread processing the scheduled destructions. + sdv::core::secure_thread m_threadScheduledConnectionDestructions; ///< Thread processing the scheduled destructions. std::atomic_bool m_bShutdown = false; ///< Set when shutting down the watchdog }; diff --git a/sdv_services/process_control/process_control.cpp b/sdv_services/process_control/process_control.cpp index 5ba14f0..cb04bbc 100644 --- a/sdv_services/process_control/process_control.cpp +++ b/sdv_services/process_control/process_control.cpp @@ -59,7 +59,7 @@ CProcessControl::~CProcessControl() bool CProcessControl::OnInitialize() { // Without monitor no trigger... - m_threadMonitor = std::thread(&CProcessControl::MonitorThread, this); + m_threadMonitor = sdv::core::secure_thread(&CProcessControl::MonitorThread, this); return true; } @@ -76,6 +76,7 @@ void CProcessControl::OnShutdown() bool CProcessControl::AllowProcessControl() const { + if (m_bEnableBypass) return true; const sdv::app::IAppContext* pAppContext = sdv::core::GetCore(); return pAppContext && (pAppContext->GetContextType() == sdv::app::EAppContext::main || pAppContext->GetContextType() == sdv::app::EAppContext::maintenance || @@ -574,3 +575,7 @@ void CProcessControl::MonitorThread() } } +void CProcessControl::EnableProcessControlAccessBypass() +{ + m_bEnableBypass = true; +} diff --git a/sdv_services/process_control/process_control.h b/sdv_services/process_control/process_control.h index 1d1334a..e3dcf96 100644 --- a/sdv_services/process_control/process_control.h +++ b/sdv_services/process_control/process_control.h @@ -118,7 +118,12 @@ public: */ virtual bool Terminate(/*in*/ sdv::process::TProcessID tProcessID) override; - private: + /** + * @brief Enable process control access (explicitly bypass application check for testing). + */ + void EnableProcessControlAccessBypass(); + +private: /** * @brief Monitor thread function. */ @@ -151,12 +156,13 @@ public: #else #error OS is not supported! #endif - mutable std::mutex m_mtxProcesses; ///< Access control for monitor map. + mutable std::mutex m_mtxProcesses; ///< Access control for monitor map. std::map> m_mapProcesses; ///< Monitor map - uint32_t m_uiNextMonCookie = 1; ///< Next monitor cookie + uint32_t m_uiNextMonCookie = 1; ///< Next monitor cookie std::map> m_mapMonitors; ///< Map with monitors. - std::atomic_bool m_bShutdown = false; ///< Set to shutdown the monitor thread. - std::thread m_threadMonitor; ///< Monitor thread. + std::atomic_bool m_bShutdown = false; ///< Set to shutdown the monitor thread. + sdv::core::secure_thread m_threadMonitor; ///< Monitor thread. + bool m_bEnableBypass = false; ///< When set, enables process control regardless of application mode. }; DEFINE_SDV_OBJECT(CProcessControl) diff --git a/sdv_services/task_timer/tasktimer.cpp b/sdv_services/task_timer/tasktimer.cpp index 8f1bf41..24f80c0 100644 --- a/sdv_services/task_timer/tasktimer.cpp +++ b/sdv_services/task_timer/tasktimer.cpp @@ -16,7 +16,7 @@ #include CTimer::CTimer(CTaskTimerService& rtimersvc, uint32_t uiPeriod, sdv::core::ITaskExecute* pExecute) : - m_rtimersvc(rtimersvc), m_pExecute(pExecute) + m_rtimersvc(rtimersvc), m_pExecute(pExecute), m_tPermissionTransferID(sdv::core::TransferCurrentPermission()) { if (!pExecute) return; @@ -35,6 +35,11 @@ CTimer::CTimer(CTaskTimerService& rtimersvc, uint32_t uiPeriod, sdv::core::ITask sev.sigev_notify_function = [](sigval sv) { CTimer* pTimer = reinterpret_cast(sv.sival_ptr); + if (pTimer->m_tPermissionTransferID) + { + sdv::core::SetAccessPermission(pTimer->m_tPermissionTransferID); + pTimer->m_tPermissionTransferID = 0u; + } std::unique_lock lock(pTimer->m_mtxExecution); if (!pTimer->m_bRunning) return; sdv::core::ITaskExecute* pExecuteLocal = reinterpret_cast(pTimer->m_pExecute); @@ -111,6 +116,11 @@ void CTimer::ExecuteCallback() { if (m_rtimersvc.GetObjectState() != sdv::EObjectState::running) return; if (!m_pExecute) return; + if (m_tPermissionTransferID) + { + sdv::core::SetAccessPermission(m_tPermissionTransferID); + m_tPermissionTransferID = 0u; + } if (!m_bPrioritySet) SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); m_bPrioritySet = true; diff --git a/sdv_services/task_timer/tasktimer.h b/sdv_services/task_timer/tasktimer.h index f6c4619..4a445c2 100644 --- a/sdv_services/task_timer/tasktimer.h +++ b/sdv_services/task_timer/tasktimer.h @@ -102,6 +102,8 @@ private: std::atomic_bool m_bRunning = false; ///< When set, the timer is running. std::mutex m_mtxExecution; ///< Prevent killing the timer when in execution. #endif + sdv::core::TPermissionTransferID m_tPermissionTransferID = 0u; ///< The transfer ID to transfer permissions between the + ///< timer creator and the execution thread. }; /** diff --git a/sdv_services/uds_unix_sockets/CMakeLists.txt b/sdv_services/uds_unix_sockets/CMakeLists.txt index 290f3c1..2fbfbe0 100644 --- a/sdv_services/uds_unix_sockets/CMakeLists.txt +++ b/sdv_services/uds_unix_sockets/CMakeLists.txt @@ -12,20 +12,55 @@ if(UNIX) # Define project project(uds_unix_sockets VERSION 1.0 LANGUAGES CXX) -# Define target -add_library(uds_unix_sockets STATIC +# Build sources once and expose them in two forms: +# - uds_unix_sockets: runtime-loadable .sdv module for ModuleControl +# - uds_unix_sockets_static: static link target for unit tests / static consumers +set(UDS_UNIX_SOCKETS_SOURCES channel_mgnt.cpp connection.cpp - ) + watchdog.cpp +) -target_link_libraries(uds_unix_sockets rt ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT}) +add_library(uds_unix_sockets SHARED + ${UDS_UNIX_SOCKETS_SOURCES} +) + +add_library(uds_unix_sockets_static STATIC + ${UDS_UNIX_SOCKETS_SOURCES} +) + +target_include_directories(uds_unix_sockets + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ./include/ +) + +target_include_directories(uds_unix_sockets_static + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ./include/ +) + +target_link_libraries(uds_unix_sockets + PUBLIC + rt + ${CMAKE_DL_LIBS} + ${CMAKE_THREAD_LIBS_INIT} +) + +target_link_libraries(uds_unix_sockets_static + PUBLIC + rt + ${CMAKE_DL_LIBS} + ${CMAKE_THREAD_LIBS_INIT} +) -target_include_directories(uds_unix_sockets PRIVATE ./include/) set_target_properties(uds_unix_sockets PROPERTIES PREFIX "") set_target_properties(uds_unix_sockets PROPERTIES SUFFIX ".sdv") # Build dependencies add_dependencies(uds_unix_sockets CompileCoreIDL) +add_dependencies(uds_unix_sockets_static CompileCoreIDL) # Appending the service in the service list set(SDV_Service_List ${SDV_Service_List} uds_unix_sockets PARENT_SCOPE) diff --git a/sdv_services/uds_unix_sockets/channel_mgnt.cpp b/sdv_services/uds_unix_sockets/channel_mgnt.cpp index a8ba4b2..a97284a 100644 --- a/sdv_services/uds_unix_sockets/channel_mgnt.cpp +++ b/sdv_services/uds_unix_sockets/channel_mgnt.cpp @@ -81,19 +81,19 @@ namespace // Directory selection (/run/user//sdv or /tmp/sdv) std::string CUnixDomainSocketsChannelMgnt::MakeUserRuntimeDir() { - std::ostringstream oss; - oss << "/run/user/" << ::getuid(); + const std::string path = "/run/ipc/sdv"; struct stat st{}; - if (::stat(oss.str().c_str(), &st) == 0) + if (::stat(path.c_str(), &st) == 0) { - std::string path = oss.str() + "/sdv"; - ::mkdir(path.c_str(), 0770); return path; } - ::mkdir("/tmp/sdv", 0770); - return "/tmp/sdv"; + // fallback if /run/ipc/sdv is not available + const std::string fallback = "/tmp/sdv"; + ::mkdir(fallback.c_str(), 0770); + + return fallback; } bool CUnixDomainSocketsChannelMgnt::OnInitialize() @@ -102,7 +102,11 @@ bool CUnixDomainSocketsChannelMgnt::OnInitialize() } void CUnixDomainSocketsChannelMgnt::OnShutdown() +{} + +void CUnixDomainSocketsChannelMgnt::OnDestroy() { + m_watchdog.Clear(); } // Endpoint creation (server) @@ -129,33 +133,133 @@ sdv::ipc::SChannelEndpoint CUnixDomainSocketsChannelMgnt::CreateEndpoint(const s path = ClampSunPath(path); - // Use a shared_ptr and store it to keep the server connection alive - auto server = std::make_shared(-1, true, path); - m_ServerConnections.push_back(server); + // Keep lifetime consistent with shared_mem: watchdog owns active connections. + std::shared_ptr server = std::make_shared(-1, true, path); + // Ignore cppcheck warning; if construction failed, an exception is expected first. + // cppcheck-suppress knownConditionTrueFalse + if (!server) + return {}; + server->SetWatchDogRemoveCallback([this](const void* connection) + { + m_watchdog.RemoveConnection(connection); + }); + m_watchdog.AddConnection(server); sdv::ipc::SChannelEndpoint ep{}; ep.pConnection = static_cast(server.get()); - ep.ssConnectString = server->GetConnectionString(); + + // Publish a Provider-wrapped connect string for compatibility with + // CCommunicationControl::CreateClientConnection() flows. + const std::string udsConnectString = server->GetConnectionString(); + ep.ssConnectString = std::string("[Provider]\n") + + "Name = \"unix_domain_sockets\"\n" + + "ConnectString = \"" + udsConnectString + "\"\n"; return ep; } // Access existing endpoint (server or client) sdv::IInterfaceAccess* CUnixDomainSocketsChannelMgnt::Access(const sdv::u8string& ssConnectString) { - const auto kv = ParseKV(static_cast(ssConnectString)); - const bool isServer = (kv.count("role") && kv.at("role") == "server"); - const std::string path = kv.count("path") ? kv.at("path") : (MakeUserRuntimeDir() + "/UDS_auto.sock"); + const std::string input = static_cast(ssConnectString); - if (isServer) + bool isServer = false; + bool parsed = false; + std::string path; + + // Parse structured TOML forms first, but only for non-raw inputs. + // Raw connect strings (proto=uds;...) are intentionally not TOML and + // would produce noisy parser diagnostics like "Missing value". + if (input.rfind("proto=uds", 0) != 0) { - auto server = std::make_shared(-1, true, path); - m_ServerConnections.push_back(server); - return static_cast(server.get()); + sdv::toml::CTOMLParser parser(input); + if (!parser.IsValid()) + return nullptr; + + const std::string providerName = parser.GetDirect("Provider.Name").GetValue(); + if (!providerName.empty()) + { + if (providerName != "unix_domain_sockets" && + providerName != "UnixSocketsChannelControl" && + providerName != "UnixDomainSocketsChannelControl") + { + return nullptr; + } + + const std::string nested = parser.GetDirect("Provider.ConnectString").GetValue(); + if (!nested.empty()) + { + const auto kv = ParseKV(nested); + if (kv.count("path")) + path = kv.at("path"); + } + + // Provider descriptions are consumed as client endpoints. + parsed = true; + isServer = false; + } + else + { + // Shared-memory style callers pass [IpcChannel] config directly to Access(). + parsed = true; + isServer = false; + } + + if (path.empty()) + { + auto pathNode = parser.GetDirect("IpcChannel.Path"); + if (pathNode.GetType() == sdv::toml::ENodeType::node_string) + { + path = static_cast(pathNode.GetValue()); + } + else + { + auto nameNode = parser.GetDirect("IpcChannel.Name"); + if (nameNode.GetType() == sdv::toml::ENodeType::node_string) + { + const std::string name = static_cast(nameNode.GetValue()); + if (!name.empty()) + path = MakeUserRuntimeDir() + "/" + name + ".sock"; + } + } + } } - // Client: allocated raw pointer (expected to be managed by SDV framework) - auto* client = new CUnixSocketConnection(-1, false, path); - return static_cast(client); + // Raw KV fallback (legacy/tests): proto=uds;role=...;path=...; + if (!parsed) + { + // Only treat as raw format when it actually starts with proto=uds. + if (input.rfind("proto=uds", 0) != 0) + return nullptr; + + const auto kv = ParseKV(input); + parsed = true; + isServer = (kv.count("role") && kv.at("role") == "server"); + if (kv.count("path")) + path = kv.at("path"); + } + + if (!parsed) + return nullptr; + + if (path.empty()) + path = MakeUserRuntimeDir() + "/UDS_auto.sock"; + + path = ClampSunPath(path); + + std::shared_ptr connection = + std::make_shared(-1, isServer, path); + // Ignore cppcheck warning; if construction failed, an exception is expected first. + // cppcheck-suppress knownConditionTrueFalse + if (!connection) + return nullptr; + + connection->SetWatchDogRemoveCallback([this](const void* instance) + { + m_watchdog.RemoveConnection(instance); + }); + + m_watchdog.AddConnection(connection); + return static_cast(connection.get()); } #endif // defined(__unix__) \ No newline at end of file diff --git a/sdv_services/uds_unix_sockets/channel_mgnt.h b/sdv_services/uds_unix_sockets/channel_mgnt.h index 258966e..7f4bfe7 100644 --- a/sdv_services/uds_unix_sockets/channel_mgnt.h +++ b/sdv_services/uds_unix_sockets/channel_mgnt.h @@ -17,6 +17,8 @@ #include #include +#include "watchdog.h" +#include class CUnixSocketConnection; @@ -37,9 +39,9 @@ public: // Object declarations DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::system_object) - DECLARE_OBJECT_CLASS_NAME("UnixDomainSocketsChannelControl") - DECLARE_OBJECT_CLASS_ALIAS("LocalChannelControl") - DECLARE_DEFAULT_OBJECT_NAME("LocalChannelControl") + DECLARE_OBJECT_CLASS_NAME("UnixSocketsChannelControl") + DECLARE_OBJECT_CLASS_ALIAS("unix_domain_sockets") + DECLARE_DEFAULT_OBJECT_NAME("unix_domain_sockets") DECLARE_OBJECT_SINGLETON() /** @@ -53,6 +55,11 @@ public: */ virtual void OnShutdown() override; + /** + * @brief Last function called before destruction. Overload of sdv::CSdvObject::OnDestroy. + */ + virtual void OnDestroy() override; + /** * @brief Create IPC connection object and return the endpoint information. Overload of * sdv::ipc::ICreateEndpoint::CreateEndpoint. @@ -80,8 +87,7 @@ private: // Helper: choose runtime dir (/run/user//sdv) or fallback (/tmp/sdv) static std::string MakeUserRuntimeDir(); - - std::vector> m_ServerConnections; + CUnixSocketsConnectionWatchDog m_watchdog; }; DEFINE_SDV_OBJECT(CUnixDomainSocketsChannelMgnt) diff --git a/sdv_services/uds_unix_sockets/connection.cpp b/sdv_services/uds_unix_sockets/connection.cpp index c28160f..9d46f64 100644 --- a/sdv_services/uds_unix_sockets/connection.cpp +++ b/sdv_services/uds_unix_sockets/connection.cpp @@ -28,6 +28,8 @@ #include #include +#include + // Anonymous namespace: local helpers (not exported) namespace { @@ -93,6 +95,8 @@ namespace { return static_cast(::getpid()); } + + } // namespace // Construction / Destruction @@ -124,6 +128,12 @@ CUnixSocketConnection::~CUnixSocketConnection() } } +void CUnixSocketConnection::SetWatchDogRemoveCallback(std::function callback) +{ + std::lock_guard lock(m_WatchdogMtx); + m_WatchdogRemoveCallback = std::move(callback); +} + // Public API std::string CUnixSocketConnection::GetConnectionString() { @@ -137,27 +147,81 @@ std::string CUnixSocketConnection::GetConnectionString() bool CUnixSocketConnection::SendSizedPacket(const void* pData, uint32_t uiDataLength) { - if (m_Fd < 0) return false; + if (m_Fd < 0) + return false; + +#ifdef MSG_NOSIGNAL + constexpr int kSendFlags = MSG_NOSIGNAL; +#else + constexpr int kSendFlags = 0; +#endif + + auto sendAll = [&](const void* data, uint32_t length) -> int + { + const char* ptr = reinterpret_cast(data); + uint32_t remaining = length; + + while (remaining > 0) + { + const ssize_t sent = ::send(m_Fd, ptr, remaining, kSendFlags); + + if (sent < 0) + { + if (errno == EINTR) + continue; + + return errno; + } + + if (sent == 0) + { + return EPIPE; + } + + ptr += sent; + remaining -= static_cast(sent); + } + + return 0; + }; const uint32_t len = uiDataLength; - std::lock_guard lock(m_SendMtx); + int sendErr = 0; - // Transport header: packet size - if (::send(m_Fd, &len, sizeof(len), 0) != static_cast(sizeof(len))) - return false; - - // SDV payload - const char* ptr = reinterpret_cast(pData); - uint32_t remain = uiDataLength; - - while (remain > 0) { - const ssize_t sent = ::send(m_Fd, ptr, remain, 0); - if (sent <= 0) return false; - ptr += sent; - remain -= static_cast(sent); + std::lock_guard lock(m_SendMtx); + + // Transport header: packet size + sendErr = sendAll(&len, static_cast(sizeof(len))); + + // SDV payload + if (sendErr == 0 && uiDataLength > 0) + { + sendErr = sendAll(pData, uiDataLength); + } } + + if (sendErr != 0) + { + if (sendErr == EPIPE || sendErr == ECONNRESET || sendErr == ENOTCONN) + { + SDV_LOG_WARNING("[UDS][TX] Peer closed connection during send, errno=", + sendErr, " (", std::strerror(sendErr), ")"); + + SetConnectState(sdv::ipc::EConnectState::disconnected); + } + else + { + SDV_LOG_WARNING("[UDS][TX] send() failed, errno=", + sendErr, " (", std::strerror(sendErr), ")"); + + SetConnectState(sdv::ipc::EConnectState::communication_error); + } + + return false; + } + return true; } @@ -233,9 +297,10 @@ bool CUnixSocketConnection::SendData(sdv::sequence>& seqDa // Header { - auto* hdr = reinterpret_cast(frame.data()); - hdr->uiVersion = SDVFrameworkInterfaceVersion; - hdr->eType = EMsgType::data; + SMsgHdr hdr{}; + hdr.uiVersion = SDVFrameworkInterfaceVersion; + hdr.eType = EMsgType::data; + std::memcpy(frame.data(), &hdr, sizeof(SMsgHdr)); msgOff = static_cast(sizeof(SMsgHdr)); } @@ -284,11 +349,12 @@ bool CUnixSocketConnection::SendData(sdv::sequence>& seqDa // Fragment header { - auto* hdr = reinterpret_cast(frame.data()); - hdr->uiVersion = SDVFrameworkInterfaceVersion; - hdr->eType = EMsgType::data_fragment; - hdr->uiTotalLength= static_cast(required); - hdr->uiOffset = offset; + SFragmentedMsgHdr hdr{}; + hdr.uiVersion = SDVFrameworkInterfaceVersion; + hdr.eType = EMsgType::data_fragment; + hdr.uiTotalLength = static_cast(required); + hdr.uiOffset = offset; + std::memcpy(frame.data(), &hdr, sizeof(SFragmentedMsgHdr)); msgOff = static_cast(sizeof(SFragmentedMsgHdr)); } @@ -330,25 +396,37 @@ bool CUnixSocketConnection::SendData(sdv::sequence>& seqDa uint64_t CUnixSocketConnection::RegisterStateEventCallback(sdv::IInterfaceAccess* pEventCallback) { - if (!pEventCallback) return 0; + if (!pEventCallback) + return 0; - auto* pCb = sdv::TInterfaceAccessPtr(pEventCallback).GetInterface(); - if (!pCb) return 0; + // use SDV interface pointer for safe casting and lifetime management + sdv::TInterfaceAccessPtr ptr(pEventCallback); - // Generate a non-zero cookie - uint64_t cookie = 0; - do - { - cookie = static_cast(::rand()); - } while (cookie == 0); - - // Write-lock: add entry + auto* pCallback = ptr.GetInterface(); + if (!pCallback) { - std::unique_lock lock(m_mtxEventCallbacks); - m_lstEventCallbacks.emplace_front(SEventCallback{ cookie, pCb }); + SDV_LOG_WARNING("[UDS] RegisterStateEventCallback: invalid callback interface"); + return 0; } - return cookie; + // check valid cookie generation (avoid 0) + uint64_t uiCookie = 0; + while (uiCookie == 0) + { + uiCookie = static_cast(rand()); + } + + // protectiong shared list with write lock + { + std::unique_lock lock(m_mtxEventCallbacks); + + m_lstEventCallbacks.emplace_front(SEventCallback{ + uiCookie, + pCallback + }); + } + + return uiCookie; } void CUnixSocketConnection::UnregisterStateEventCallback(uint64_t uiCookie) @@ -381,12 +459,25 @@ void CUnixSocketConnection::UnregisterStateEventCallback(uint64_t uiCookie) bool CUnixSocketConnection::AsyncConnect(sdv::IInterfaceAccess* pReceiver) { + const auto currentState = m_eConnectState.load(std::memory_order_acquire); + if (currentState == sdv::ipc::EConnectState::connected) + { + return true; + } + + if (currentState == sdv::ipc::EConnectState::initializing && m_ConnectThread.joinable()) + { + SDV_LOG_WARNING("[UDS] AsyncConnect ignored: connect worker already running"); + return false; + } + // Capture callbacks under lock { std::lock_guard lk(m_StateMtx); m_pReceiver = sdv::TInterfaceAccessPtr(pReceiver).GetInterface(); m_pEvent = sdv::TInterfaceAccessPtr(pReceiver).GetInterface(); m_eConnectState = sdv::ipc::EConnectState::initializing; + m_bConnectedOnce.store(false, std::memory_order_release); // Reset stop flags m_StopReceiveThread.store(false); @@ -394,11 +485,12 @@ bool CUnixSocketConnection::AsyncConnect(sdv::IInterfaceAccess* pReceiver) } m_StateCv.notify_all(); - // If an old worker exists, join it to avoid duplicates + // If an old worker exists, join it to avoid duplicates. + // Joining an in-progress worker here can deadlock when a previous connect attempt is still pending. if (m_ConnectThread.joinable()) m_ConnectThread.join(); // Start the unique connect worker (server/client) - m_ConnectThread = std::thread(&CUnixSocketConnection::ConnectWorker, this); + m_ConnectThread = sdv::core::secure_thread(&CUnixSocketConnection::ConnectWorker, this); return true; } @@ -448,35 +540,100 @@ int CUnixSocketConnection::AcceptConnection() // deprecated return clientFd; } +void CUnixSocketConnection::CloseClientSocketOnly() +{ + const int fd = m_Fd; + m_Fd = -1; + + if (fd >= 0) + { + ::shutdown(fd, SHUT_RDWR); + ::close(fd); + } +} + + +void CUnixSocketConnection::WaitForClientEnd() +{ + std::unique_lock lock(m_MtxConnect); + + m_cvConnect.wait(lock, [this] + { + const auto state = m_eConnectState.load(std::memory_order_acquire); + + return m_StopConnectThread.load(std::memory_order_acquire) || + state == sdv::ipc::EConnectState::disconnected || + state == sdv::ipc::EConnectState::disconnected_forced || + state == sdv::ipc::EConnectState::communication_error || + state == sdv::ipc::EConnectState::connection_error; + }); + +} + bool CUnixSocketConnection::WaitForConnection(uint32_t uiWaitMs) { - if (m_eConnectState.load(std::memory_order_acquire) == sdv::ipc::EConnectState::connected) return true; +#if ENABLE_REPORTING >= 1 + if (m_eConnectState == sdv::ipc::EConnectState::connected) + TRACE("Not waiting for a connection - already connected"); + else + TRACE("Waiting for a connection of ", uiWaitMs, "ms"); +#endif - std::unique_lock lk(m_MtxConnect); + std::unique_lock lock(m_MtxConnect); - if (uiWaitMs == 0xFFFFFFFFu) + auto isConnected = [&]() { - m_CvConnect.wait(lk, [this]{ - return m_eConnectState.load(std::memory_order_acquire) == sdv::ipc::EConnectState::connected; - }); - return true; - } - if (uiWaitMs == 0u) - return (m_eConnectState.load(std::memory_order_acquire) == sdv::ipc::EConnectState::connected); + return m_eConnectState.load(std::memory_order_acquire) == + sdv::ipc::EConnectState::connected; + }; - return m_CvConnect.wait_for(lk, std::chrono::milliseconds(uiWaitMs), - [this]{ return m_eConnectState.load(std::memory_order_acquire) == sdv::ipc::EConnectState::connected; }); + + if (isConnected()) + return true; + + // Wait for the connection to take place. + // Attention: sporadic + if (uiWaitMs) + m_cvConnect.wait_for(lock, std::chrono::milliseconds(uiWaitMs), isConnected); + +#if ENABLE_REPORTING >= 1 + if (m_eConnectState == sdv::ipc::EConnectState::connected) + TRACE("Waiting finished - connection established"); + else + TRACE("Waiting finished - timeout occurred"); +#endif + + return m_eConnectState == sdv::ipc::EConnectState::connected; } void CUnixSocketConnection::CancelWait() { - // optional: m_CvConnect.notify_all(); +#if ENABLE_REPORTING >= 1 + TRACE("Cancel the (potential) waiting for a connection"); +#endif + + m_cvConnect.notify_all(); } void CUnixSocketConnection::Disconnect() { - StopThreadsAndCloseSockets(/*unlinkPath*/ false); + + // Try to notify peer before closing the socket. + if (m_Fd >= 0 && + m_eConnectState.load(std::memory_order_acquire) == sdv::ipc::EConnectState::connected) + { + SendControlMessage(EMsgType::connect_term); + } + + // Notify listeners before entering teardown mode. SetConnectState(sdv::ipc::EConnectState::disconnected); + m_cvConnect.notify_all(); + + m_StopReceiveThread.store(true, std::memory_order_release); + m_StopConnectThread.store(true, std::memory_order_release); + m_cvConnect.notify_all(); // wake WaitForClientEnd before joins + + StopThreadsAndCloseSockets(/*unlinkPath*/ false); } sdv::ipc::EConnectState CUnixSocketConnection::GetConnectState() const @@ -486,28 +643,58 @@ sdv::ipc::EConnectState CUnixSocketConnection::GetConnectState() const void CUnixSocketConnection::DestroyObject() { + bool expected = false; + if (!m_DestroyObjectCalled.compare_exchange_strong(expected, true)) + { + return; + } + m_StopReceiveThread.store(true); - m_eConnectState = sdv::ipc::EConnectState::disconnected; + m_StopConnectThread.store(true); + SetConnectState(sdv::ipc::EConnectState::terminating); + + // Perform full teardown (threads/sockets/state/waiters). + Disconnect(); + + std::function removeCallback; + { + std::lock_guard lock(m_WatchdogMtx); + removeCallback = std::move(m_WatchdogRemoveCallback); + } + if (removeCallback) + { + removeCallback(this); + } } void CUnixSocketConnection::SetConnectState(sdv::ipc::EConnectState eConnectState) { + if (eConnectState == sdv::ipc::EConnectState::connected) + m_bConnectedOnce.store(true, std::memory_order_release); + // Update internal state atomically and wake up waiters. { std::lock_guard lk(m_MtxConnect); m_eConnectState.store(eConnectState, std::memory_order_release); } - m_CvConnect.notify_all(); + + // During teardown, owner objects may already be destructing. Do not callback then. + const bool bTeardown = m_StopReceiveThread.load(std::memory_order_acquire) || + m_StopConnectThread.load(std::memory_order_acquire); // Notify the legacy single-listener (kept for backward compatibility). - try + if (!bTeardown && m_pEvent) { - m_pEvent->SetConnectState(eConnectState); + try + { + m_pEvent->SetConnectState(eConnectState); + } + catch (...) { /* swallow: user callback must not crash transport */ } } - catch (...) { /* swallow: user callback must not crash transport */ } // Notify all registered listeners from the registry (read-mostly path). bool needCompact = false; + if (!bTeardown) { std::shared_lock rlock(m_mtxEventCallbacks); for (const auto& entry : m_lstEventCallbacks) @@ -522,7 +709,7 @@ void CUnixSocketConnection::SetConnectState(sdv::ipc::EConnectState eConnectStat } // Compact registry if we saw null entries (write path). - if (needCompact) + if (!bTeardown && needCompact) { std::unique_lock wlock(m_mtxEventCallbacks); m_lstEventCallbacks.remove_if([](const SEventCallback& e){ return e.pCallback == nullptr; }); @@ -601,13 +788,29 @@ void CUnixSocketConnection::ConnectWorker() } ::unlink(m_UdsPath.c_str()); - sockaddr_un addr{}; addr.sun_family = AF_UNIX; - ::snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", m_UdsPath.c_str()); - if (::bind(m_ListenFd, reinterpret_cast(&addr), sizeof(addr)) < 0) + sockaddr_un addr{}; + addr.sun_family = AF_UNIX; + + if (m_UdsPath.size() >= sizeof(addr.sun_path)) + { + SDV_LOG_ERROR("[UDS] Path too long: ", m_UdsPath, " (max ", sizeof(addr.sun_path) - 1, ")"); + ::close(m_ListenFd); + m_ListenFd = -1; + SetConnectState(sdv::ipc::EConnectState::connection_error); + return; + } + + strncpy(addr.sun_path, m_UdsPath.c_str(), sizeof(addr.sun_path) - 1); + addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; + + socklen_t len = offsetof(sockaddr_un, sun_path) + strlen(addr.sun_path) + 1; + + if (::bind(m_ListenFd, reinterpret_cast(&addr), len) < 0) { SDV_LOG_ERROR("[UDS][Server] bind('", m_UdsPath, "') failed: ", std::strerror(errno)); - ::close(m_ListenFd); m_ListenFd = -1; + ::close(m_ListenFd); + m_ListenFd = -1; SetConnectState(sdv::ipc::EConnectState::connection_error); return; } @@ -617,71 +820,109 @@ void CUnixSocketConnection::ConnectWorker() if (::listen(m_ListenFd, 1) < 0) { SDV_LOG_ERROR("[UDS][Server] listen() failed: ", std::strerror(errno)); - ::close(m_ListenFd); m_ListenFd = -1; + ::close(m_ListenFd); + m_ListenFd = -1; SetConnectState(sdv::ipc::EConnectState::connection_error); return; } -#if ENABLE_REPORTING >= 1 + #if ENABLE_REPORTING >= 1 TRACE("[UDS][Server] Listening on path ", m_UdsPath); -#endif + #endif - // Cancellable accept loop (poll) - int clientFd = -1; + SetConnectState(sdv::ipc::EConnectState::initializing); + + // Keep listener alive and accept clients repeatedly. while (!m_StopConnectThread.load()) { - struct pollfd pfd{ m_ListenFd, POLLIN, 0 }; - const int pr = ::poll(&pfd, 1, /*timeout_ms*/ 100); - if (pr < 0) { if (errno == EINTR) continue; SDV_LOG_WARNING("[UDS][Server] poll() error: ", std::strerror(errno)); break; } - if (pr == 0) continue; + int clientFd = -1; + bool acceptFatal = false; - if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) + // Cancellable accept loop + while (!m_StopConnectThread.load()) { -#if ENABLE_REPORTING >= 1 - TRACE("[UDS][Server] Listening socket closed/invalidated"); -#endif - break; - } + struct pollfd pfd{ m_ListenFd, POLLIN, 0 }; + const int pr = ::poll(&pfd, 1, /*timeout_ms*/ 10); - if (pfd.revents & POLLIN) - { - clientFd = ::accept(m_ListenFd, nullptr, nullptr); - if (clientFd < 0) + if (pr < 0) { if (errno == EINTR) continue; if (m_StopConnectThread.load()) break; - SDV_LOG_ERROR("[UDS][Server] accept() failed: ", std::strerror(errno)); + SDV_LOG_WARNING("[UDS][Server] poll() error: ", std::strerror(errno)); + acceptFatal = true; break; } - break; // accepted + + if (pr == 0) continue; + + if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) + { + #if ENABLE_REPORTING >= 1 + TRACE("[UDS][Server] Listening socket closed/invalidated"); + #endif + if (!m_StopConnectThread.load()) + acceptFatal = true; + break; + } + + if (pfd.revents & POLLIN) + { + clientFd = ::accept(m_ListenFd, nullptr, nullptr); + + if (clientFd < 0) + { + if (errno == EINTR) continue; + if (m_StopConnectThread.load()) break; + SDV_LOG_ERROR("[UDS][Server] accept() failed: ", std::strerror(errno)); + acceptFatal = true; + break; + } + break; // one client accepted + } + } + + if (m_StopConnectThread.load()) + break; + + if (acceptFatal || clientFd < 0) + { + SetConnectState(sdv::ipc::EConnectState::connection_error); + break; + } + + // Serve one client session + m_Fd = clientFd; + SetConnectState(sdv::ipc::EConnectState::initialized); + StartReceiveThread_Unsafe(); + + // Wait until that client disconnects or teardown is requested + WaitForClientEnd(); + CloseClientSocketOnly(); + + if (!m_StopConnectThread.load()) + { + // Re-arm state for next accept + SetConnectState(sdv::ipc::EConnectState::initializing); } } - // Close listen fd (safe if already closed elsewhere) - const int lfd = m_ListenFd; m_ListenFd = -1; + // Close listen FD on final shutdown/error + const int lfd = m_ListenFd; + m_ListenFd = -1; if (lfd >= 0) ::close(lfd); if (m_StopConnectThread.load()) { -#if ENABLE_REPORTING >= 1 + #if ENABLE_REPORTING >= 1 TRACE("[UDS][Server] ConnectWorker stopped intentionally"); -#endif - return; // intentional stop - } - if (clientFd < 0) - { - SetConnectState(sdv::ipc::EConnectState::connection_error); - return; + #endif } - m_Fd = clientFd; - SetConnectState(sdv::ipc::EConnectState::connected); - StartReceiveThread_Unsafe(); return; } else { - // --- CLIENT --- + // --- CLIENT --- m_Fd = ::socket(AF_UNIX, SOCK_STREAM, 0); if (m_Fd < 0) { @@ -690,8 +931,22 @@ void CUnixSocketConnection::ConnectWorker() return; } - sockaddr_un addr{}; addr.sun_family = AF_UNIX; - ::snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", m_UdsPath.c_str()); + sockaddr_un addr{}; + addr.sun_family = AF_UNIX; + + if (m_UdsPath.size() >= sizeof(addr.sun_path)) + { + SDV_LOG_ERROR("[UDS] Path too long: ", m_UdsPath, " (max ", sizeof(addr.sun_path) - 1, ")"); + ::close(m_Fd); + m_Fd = -1; + SetConnectState(sdv::ipc::EConnectState::connection_error); + return; + } + + strncpy(addr.sun_path, m_UdsPath.c_str(), sizeof(addr.sun_path) - 1); + addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; + + socklen_t len = offsetof(sockaddr_un, sun_path) + strlen(addr.sun_path) + 1; #if ENABLE_REPORTING >= 1 TRACE("[UDS][Client] Connecting to ", m_UdsPath); @@ -700,62 +955,55 @@ void CUnixSocketConnection::ConnectWorker() int attempts = 10; while (attempts-- > 0 && !m_StopConnectThread.load()) { - if (::connect(m_Fd, reinterpret_cast(&addr), sizeof(addr)) == 0) + if (::connect(m_Fd, reinterpret_cast(&addr), len) == 0) { - SetConnectState(sdv::ipc::EConnectState::connected); -#if ENABLE_REPORTING >= 1 - TRACE("[UDS][Client] Connected"); -#endif + SetConnectState(sdv::ipc::EConnectState::initialized); StartReceiveThread_Unsafe(); return; } + else + { + SDV_LOG_WARNING("Connect failed: ", strerror(errno)); + } + std::this_thread::sleep_for(std::chrono::milliseconds(200)); } - if (!m_StopConnectThread.load()) + // Disconnect() requested while retries were running: do not overwrite state with connection_error. + if (m_StopConnectThread.load()) { - SDV_LOG_WARNING("[UDS][Client] connect() timeout to '", m_UdsPath, - "', last errno=", errno, " (", std::strerror(errno), ")"); + ::close(m_Fd); + m_Fd = -1; + return; } - ::close(m_Fd); m_Fd = -1; + SDV_LOG_WARNING("[UDS][Client] connect() timeout to '", m_UdsPath, + "', last errno=", errno, " (", std::strerror(errno), ")"); + + ::close(m_Fd); + m_Fd = -1; SetConnectState(sdv::ipc::EConnectState::connection_error); return; } } catch (const std::exception& ex) { - SDV_LOG_ERROR("[UDS][ConnectWorker] exception: ", ex.what()); - SetConnectState(sdv::ipc::EConnectState::connection_error); + if (!m_StopConnectThread.load(std::memory_order_acquire)) + { + SDV_LOG_ERROR("[UDS][ConnectWorker] exception: ", ex.what()); + SetConnectState(sdv::ipc::EConnectState::connection_error); + } } catch (...) { - SDV_LOG_ERROR("[UDS][ConnectWorker] unknown exception"); - SetConnectState(sdv::ipc::EConnectState::connection_error); + if (!m_StopConnectThread.load(std::memory_order_acquire)) + { + SDV_LOG_ERROR("[UDS][ConnectWorker] unknown exception"); + SetConnectState(sdv::ipc::EConnectState::connection_error); + } } } -// Receive thread and SDV state machine -void CUnixSocketConnection::ReceiveSyncAnswer(const CMessage& message) -{ - const auto hdr = message.GetMsgHdr(); - if (hdr.uiVersion != SDVFrameworkInterfaceVersion) - { - SetConnectState(sdv::ipc::EConnectState::communication_error); - SDV_LOG_WARNING("[UDS][RX] sync_answer with invalid version"); - return; - } - - // Client -> send connect_request - SConnectMsg msg{}; - msg.uiVersion = SDVFrameworkInterfaceVersion; - msg.eType = EMsgType::connect_request; - msg.tProcessID = GetCurrentProcessID_Local(); - - if (!SendSizedPacket(&msg, sizeof(msg))) - SDV_LOG_ERROR("[UDS][RX] Failed to send connect_request in response to sync_answer"); -} - void CUnixSocketConnection::ReceiveMessages() { try @@ -789,34 +1037,69 @@ void CUnixSocketConnection::ReceiveMessages() if (pr == 0) { - if (!m_AcceptConnectionRequired && (m_eConnectState == sdv::ipc::EConnectState::initialized)) + if (!m_AcceptConnectionRequired && + (m_eConnectState == sdv::ipc::EConnectState::initialized || + m_eConnectState == sdv::ipc::EConnectState::disconnected)) { auto now = std::chrono::high_resolution_clock::now(); if (std::chrono::duration(now - tpStart).count() > 0.5) { tpStart = now; - SMsgHdr sync{ SDVFrameworkInterfaceVersion, EMsgType::sync_request }; - if (!SendSizedPacket(&sync, static_cast(sizeof(sync)))) - SDV_LOG_WARNING("[UDS][RX] Failed to send periodic sync_request (client pacing)"); + + if (!SendControlMessage(EMsgType::sync_request)) + { + SDV_LOG_WARNING("[UDS][RX] Failed to send periodic sync_request"); + } } } continue; } - - if (pr < 0 || (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) != 0) + if (pr < 0) { SetConnectState(sdv::ipc::EConnectState::disconnected); - SDV_LOG_WARNING("[UDS][RX] poll() hangup/error -> disconnected"); + SDV_LOG_WARNING("[UDS][RX] poll() failed -> disconnected, errno=", errno, + " (", std::strerror(errno), ")"); break; } - if ((pfd.revents & POLLIN) == 0) continue; + + const short revents = pfd.revents; + const bool bHasPollIn = (revents & POLLIN) != 0; + const bool bHasPollErr = (revents & (POLLERR | POLLNVAL)) != 0; + const bool bHasPollHup = (revents & POLLHUP) != 0; + const bool bHasPollNval = (revents & POLLNVAL) != 0; + + // POLLNVAL commonly happens when another thread closed/replaced the FD during local shutdown. + // In this case treat it as an expected teardown signal. + if (bHasPollNval && (m_StopReceiveThread.load() || m_Fd < 0)) + { + SetConnectState(sdv::ipc::EConnectState::disconnected); + break; + } + + // If POLLIN is present together with HUP/ERR, consume pending data first. + // This avoids dropping the last frame when peer closes after sending. + if (bHasPollErr && !bHasPollIn) + { + SetConnectState(sdv::ipc::EConnectState::disconnected); + break; + } + + if (bHasPollHup && !bHasPollIn) + { + SetConnectState(sdv::ipc::EConnectState::disconnected); + break; + } + + if (!bHasPollIn) continue; + // Transport header uint32_t packetSize = 0; if (!ReadTransportHeader(packetSize)) { SetConnectState(sdv::ipc::EConnectState::disconnected); - SDV_LOG_WARNING("[UDS][RX] Invalid/missing transport header -> disconnected"); + if (!bHasPollHup) + SDV_LOG_WARNING("[UDS][RX] Invalid/missing transport header -> disconnected"); break; } @@ -825,7 +1108,8 @@ void CUnixSocketConnection::ReceiveMessages() if (!ReadNumberOfBytes(reinterpret_cast(payload.data()), packetSize)) { SetConnectState(sdv::ipc::EConnectState::disconnected); - SDV_LOG_WARNING("[UDS][RX] Incomplete payload read -> disconnected"); + if (!bHasPollHup) + SDV_LOG_WARNING("[UDS][RX] Incomplete payload read -> disconnected"); break; } @@ -886,61 +1170,168 @@ void CUnixSocketConnection::ReceiveMessages() void CUnixSocketConnection::ReceiveSyncRequest(const CMessage& message) { const auto hdr = message.GetMsgHdr(); + if (hdr.uiVersion != SDVFrameworkInterfaceVersion) { SetConnectState(sdv::ipc::EConnectState::communication_error); - SDV_LOG_WARNING("[UDS][RX] sync_request with invalid version"); return; } - // Reply with sync_answer - SMsgHdr reply{}; - reply.uiVersion = SDVFrameworkInterfaceVersion; - reply.eType = EMsgType::sync_answer; + if (!IsServer()) + return; + + SetConnectState(sdv::ipc::EConnectState::connecting); + + if (!SendControlMessage(EMsgType::sync_answer)) + { + SetConnectState(sdv::ipc::EConnectState::communication_error); + return; + } + +} + +// Receive thread and SDV state machine +void CUnixSocketConnection::ReceiveSyncAnswer(const CMessage& message) +{ + const auto hdr = message.GetMsgHdr(); + if (hdr.uiVersion != SDVFrameworkInterfaceVersion) + { + SetConnectState(sdv::ipc::EConnectState::communication_error); + SDV_LOG_WARNING("[UDS][RX] sync_answer with invalid version"); + return; + } + + if (IsServer()) + return; + + SetConnectState(sdv::ipc::EConnectState::negotiating); + + if (!SendConnectMessage(EMsgType::connect_request)) + { + SetConnectState(sdv::ipc::EConnectState::communication_error); + return; + } - if (!SendSizedPacket(&reply, sizeof(reply))) - SDV_LOG_ERROR("[UDS][RX] Failed to send sync_answer to sync_request"); } void CUnixSocketConnection::ReceiveConnectRequest(const CMessage& message) { - const auto hdr = message.GetConnectHdr(); - if (hdr.uiVersion != SDVFrameworkInterfaceVersion) - { - SetConnectState(sdv::ipc::EConnectState::communication_error); - SDV_LOG_WARNING("[UDS][RX] connect_request with invalid version"); - return; - } + //if (m_eConnectState == sdv::ipc::EConnectState::connecting) + //{ + const auto hdr = message.GetConnectHdr(); + if (hdr.uiVersion != SDVFrameworkInterfaceVersion) + { + SetConnectState(sdv::ipc::EConnectState::communication_error); + SDV_LOG_WARNING("[UDS][RX] connect_request with invalid version"); + return; + } - // Reply with connect_answer - SConnectMsg reply{}; - reply.uiVersion = SDVFrameworkInterfaceVersion; - reply.eType = EMsgType::connect_answer; - reply.tProcessID = GetCurrentProcessID_Local(); + // connect_request must be handled only by server + if (!IsServer()) + { + SDV_LOG_WARNING("[UDS][RX] Client received connect_request - ignored"); + return; + } + + if (!SendConnectMessage(EMsgType::connect_answer)) + { + SDV_LOG_ERROR("[UDS][RX] Failed to send connect_answer"); + SetConnectState(sdv::ipc::EConnectState::communication_error); + return; + } - if (!SendSizedPacket(&reply, sizeof(reply))) - SDV_LOG_ERROR("[UDS][RX] Failed to send connect_answer"); + SetConnectState(sdv::ipc::EConnectState::connected); + m_cvConnect.notify_all(); + +#if ENABLE_REPORTING >= 1 + TRACE("Trigger connected"); +#endif + + //} } void CUnixSocketConnection::ReceiveConnectAnswer(const CMessage& message) { - const auto hdr = message.GetConnectHdr(); - if (hdr.uiVersion != SDVFrameworkInterfaceVersion) + //if (m_eConnectState == sdv::ipc::EConnectState::negotiating) + //{ + const auto hdr = message.GetConnectHdr(); + if (hdr.uiVersion != SDVFrameworkInterfaceVersion) + { + SetConnectState(sdv::ipc::EConnectState::communication_error); + SDV_LOG_WARNING("[UDS][RX] connect_answer with invalid version"); + return; + } + // connect_answer must be handled only by client + if (IsServer()) + { + SDV_LOG_WARNING("[UDS][RX] Server received connect_answer - ignored"); + return; + } + + SetConnectState(sdv::ipc::EConnectState::connected); + m_cvConnect.notify_all(); +#if ENABLE_REPORTING >= 1 + TRACE("[UDS][RX] Client connected after connect_answer"); +#endif + + //} + +} + +bool CUnixSocketConnection::SendControlMessage(EMsgType type) +{ + // Only simple header-based control messages are allowed here. + if (type != EMsgType::sync_request && + type != EMsgType::sync_answer && + type != EMsgType::connect_term) { - SetConnectState(sdv::ipc::EConnectState::communication_error); - SDV_LOG_WARNING("[UDS][RX] connect_answer with invalid version"); - return; + SDV_LOG_ERROR("[UDS][TX] Invalid control message type"); + return false; } - // Fully established - SetConnectState(sdv::ipc::EConnectState::connected); + SMsgHdr msg{}; + msg.uiVersion = SDVFrameworkInterfaceVersion; + msg.eType = type; + +#if ENABLE_REPORTING >= 1 + TRACE("[UDS][TX] Send control message type=", static_cast(type)); +#endif + + return SendSizedPacket(&msg, sizeof(msg)); +} + +bool CUnixSocketConnection::SendConnectMessage(EMsgType type) +{ + if (type != EMsgType::connect_request && + type != EMsgType::connect_answer) + { + SDV_LOG_ERROR("[UDS][TX] Invalid connect message type"); + return false; + } + + SConnectMsg msg{}; + msg.uiVersion = SDVFrameworkInterfaceVersion; + msg.eType = type; + msg.tProcessID = GetCurrentProcessID_Local(); + +#if ENABLE_REPORTING >= 1 + TRACE("[UDS][TX] Send ", + type == EMsgType::connect_request ? "connect_request" : "connect_answer", + " pid=", msg.tProcessID); +#endif + + return SendSizedPacket(&msg, sizeof(msg)); } void CUnixSocketConnection::ReceiveConnectTerm(const CMessage& /*message*/) { // Peer requested termination SetConnectState(sdv::ipc::EConnectState::disconnected); - m_StopReceiveThread.store(true); + // m_rWatchDog.RemoveMonitor(this); + + m_StopReceiveThread.store(true, std::memory_order_release); + m_cvConnect.notify_all(); + } void CUnixSocketConnection::StartReceiveThread_Unsafe() @@ -954,15 +1345,29 @@ void CUnixSocketConnection::StartReceiveThread_Unsafe() } m_StopReceiveThread.store(false); - m_ReceiveThread = std::thread(&CUnixSocketConnection::ReceiveMessages, this); + m_ReceiveThread = sdv::core::secure_thread(&CUnixSocketConnection::ReceiveMessages, this); } void CUnixSocketConnection::StopThreadsAndCloseSockets(bool unlinkPath) { + + { + // Prevent callbacks into potentially destroyed owners during teardown. + std::lock_guard lk(m_StateMtx); + m_pReceiver = nullptr; + m_pEvent = nullptr; + } + { + std::unique_lock wlock(m_mtxEventCallbacks); + m_lstEventCallbacks.clear(); + } + // Signal stop m_StopReceiveThread.store(true); m_StopConnectThread.store(true); + m_cvConnect.notify_all(); // defensive wake-up for any waiter + // Close listen FD to break accept() const int lfd = m_ListenFd; m_ListenFd = -1; if (lfd >= 0) ::close(lfd); @@ -1077,7 +1482,8 @@ uint32_t CUnixSocketConnection::ReadDataTable(const CMessage& rMessage, SDataCon // Buffer count if (rMessage.GetSize() < (uiOffset + static_cast(sizeof(uint32_t)))) return 0; - const uint32_t uiAmount = *reinterpret_cast(rMessage.GetData() + uiOffset); + uint32_t uiAmount = 0; + std::memcpy(&uiAmount, rMessage.GetData() + uiOffset, sizeof(uiAmount)); uiOffset += sizeof(uint32_t); rsDataCtxt.uiCurrentOffset += sizeof(uint32_t); @@ -1089,7 +1495,8 @@ uint32_t CUnixSocketConnection::ReadDataTable(const CMessage& rMessage, SDataCon for (uint32_t i = 0; i < uiAmount; ++i) { - const uint32_t sz = *reinterpret_cast(rMessage.GetData() + uiOffset); + uint32_t sz = 0; + std::memcpy(&sz, rMessage.GetData() + uiOffset, sizeof(sz)); sizes.push_back(static_cast(sz)); uiOffset += sizeof(uint32_t); rsDataCtxt.uiCurrentOffset += sizeof(uint32_t); @@ -1163,4 +1570,4 @@ bool CUnixSocketConnection::ReadDataChunk(const CMessage& rMessage, uint32_t uiO return true; } -#endif // defined(__unix__) +#endif // defined(__unix__) \ No newline at end of file diff --git a/sdv_services/uds_unix_sockets/connection.h b/sdv_services/uds_unix_sockets/connection.h index 1ce1730..979c296 100644 --- a/sdv_services/uds_unix_sockets/connection.h +++ b/sdv_services/uds_unix_sockets/connection.h @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include #include #include @@ -98,6 +100,9 @@ public: /** @brief Destroy object (IObjectDestroy). */ void DestroyObject() override; + /** @brief Register callback used to remove this connection from manager watchdog storage. */ + void SetWatchDogRemoveCallback(std::function callback); + /** @brief Set state and notify listeners (callback-safe). */ void SetConnectState(sdv::ipc::EConnectState eConnectState); @@ -153,21 +158,27 @@ public: SMsgHdr GetMsgHdr() const { if (GetSize() < sizeof(SMsgHdr)) return SMsgHdr{0, EMsgType::connect_term}; - return *reinterpret_cast(GetData()); + SMsgHdr hdr{}; + std::memcpy(&hdr, GetData(), sizeof(SMsgHdr)); + return hdr; } /** @return SDV connect header (or default if undersized). */ SConnectMsg GetConnectHdr() const { if (GetSize() < sizeof(SConnectMsg)) return SConnectMsg{}; - return *reinterpret_cast(GetData()); + SConnectMsg hdr{}; + std::memcpy(&hdr, GetData(), sizeof(SConnectMsg)); + return hdr; } /** @return SDV fragmented header (or default if undersized). */ SFragmentedMsgHdr GetFragmentedHdr() const { if (GetSize() < sizeof(SFragmentedMsgHdr)) return SFragmentedMsgHdr{}; - return *reinterpret_cast(GetData()); + SFragmentedMsgHdr hdr{}; + std::memcpy(&hdr, GetData(), sizeof(SFragmentedMsgHdr)); + return hdr; } /** @return true if the SDV envelope is well-formed. */ @@ -253,6 +264,10 @@ public: */ void ReceiveMessages(); + bool SendConnectMessage(EMsgType type); + + bool SendControlMessage(EMsgType type); + /** * @brief Handle an incoming sync_request message. * @@ -360,6 +375,10 @@ public: */ void StopThreadsAndCloseSockets(bool unlinkPath); + void WaitForClientEnd(); + + void CloseClientSocketOnly(); + private: //Transport state int m_Fd { -1 }; ///< Active connection FD. @@ -370,19 +389,24 @@ private: //Threads & control std::atomic m_StopReceiveThread { false }; std::atomic m_StopConnectThread { false }; - std::thread m_ReceiveThread; - std::thread m_ConnectThread; + sdv::core::secure_thread m_ReceiveThread; + sdv::core::secure_thread m_ConnectThread; //State & synchronization std::condition_variable m_StateCv; std::atomic m_eConnectState { sdv::ipc::EConnectState::uninitialized }; + std::atomic m_bConnectedOnce { false }; ///< Latched true once a connection was established. sdv::ipc::IDataReceiveCallback* m_pReceiver { nullptr }; sdv::ipc::IConnectEventCallback* m_pEvent { nullptr }; std::mutex m_MtxConnect; - std::condition_variable m_CvConnect; + std::condition_variable m_cvConnect; std::mutex m_StateMtx; ///< Protects receiver/event assignment. std::list m_lstEventCallbacks; ///< List containing event callbacks std::shared_mutex m_mtxEventCallbacks; ///< Protect access to callback list + std::mutex m_WatchdogMtx; + std::function m_WatchdogRemoveCallback; + std::atomic m_DestroyObjectCalled { false }; + std::atomic m_CancelWait { false }; //TX synchronization std::mutex m_SendMtx; diff --git a/sdv_services/uds_unix_sockets/watchdog.cpp b/sdv_services/uds_unix_sockets/watchdog.cpp new file mode 100644 index 0000000..98c141f --- /dev/null +++ b/sdv_services/uds_unix_sockets/watchdog.cpp @@ -0,0 +1,58 @@ +/******************************************************************************** +* Copyright (c) 2025-2026 ZF Friedrichshafen AG +* +* This program and the accompanying materials are made available under the +* terms of the Apache License Version 2.0 which is available at +* https://www.apache.org/licenses/LICENSE-2.0 +* +* SPDX-License-Identifier: Apache-2.0 +* +* Contributors: +* Denisa Ros - initial API and implementation +********************************************************************************/ + +#if defined(__unix__) + +#include "watchdog.h" + +void CUnixSocketsConnectionWatchDog::AddConnectionImpl(const std::shared_ptr& connection) +{ + std::lock_guard lock(m_Mutex); + m_Connections[connection.get()] = connection; +} + +void CUnixSocketsConnectionWatchDog::RemoveConnection(const void* connection) +{ + if (!connection) + { + return; + } + + std::shared_ptr removed; + { + std::lock_guard lock(m_Mutex); + auto it = m_Connections.find(connection); + if (it == m_Connections.end()) + { + return; + } + + removed = std::move(it->second); + m_Connections.erase(it); + } + + removed.reset(); +} + +void CUnixSocketsConnectionWatchDog::Clear() +{ + std::map> localConnections; + { + std::lock_guard lock(m_Mutex); + localConnections.swap(m_Connections); + } + + localConnections.clear(); +} + +#endif // defined(__unix__) diff --git a/sdv_services/uds_unix_sockets/watchdog.h b/sdv_services/uds_unix_sockets/watchdog.h new file mode 100644 index 0000000..4cb6abb --- /dev/null +++ b/sdv_services/uds_unix_sockets/watchdog.h @@ -0,0 +1,47 @@ +/******************************************************************************** +* Copyright (c) 2025-2026 ZF Friedrichshafen AG +* +* This program and the accompanying materials are made available under the +* terms of the Apache License Version 2.0 which is available at +* https://www.apache.org/licenses/LICENSE-2.0 +* +* SPDX-License-Identifier: Apache-2.0 +* +* Contributors: +* Denisa Ros - initial API and implementation +********************************************************************************/ + +#if defined(__unix__) +#ifndef UDS_UNIX_SOCKETS_WATCHDOG_H +#define UDS_UNIX_SOCKETS_WATCHDOG_H + +#include +#include +#include + +class CUnixSocketsConnectionWatchDog +{ +public: + template + void AddConnection(const std::shared_ptr& connection) + { + if (!connection) + { + return; + } + + AddConnectionImpl(std::static_pointer_cast(connection)); + } + + void RemoveConnection(const void* connection); + void Clear(); + +private: + void AddConnectionImpl(const std::shared_ptr& connection); + + std::mutex m_Mutex; + std::map> m_Connections; +}; + +#endif // UDS_UNIX_SOCKETS_WATCHDOG_H +#endif // defined(__unix__) diff --git a/sdv_services/uds_unix_tunnel/CMakeLists.txt b/sdv_services/uds_unix_tunnel/CMakeLists.txt index f40e862..db2700c 100644 --- a/sdv_services/uds_unix_tunnel/CMakeLists.txt +++ b/sdv_services/uds_unix_tunnel/CMakeLists.txt @@ -12,22 +12,57 @@ if(UNIX) # Define project project(uds_unix_tunnel VERSION 1.0 LANGUAGES CXX) -# Define target -add_library(uds_unix_tunnel STATIC +set(UDS_UNIX_TUNNEL_SOURCES channel_mgnt.cpp connection.cpp + watchdog.cpp +) + +# Define targets +add_library(uds_unix_tunnel STATIC + ${UDS_UNIX_TUNNEL_SOURCES} ) -target_link_libraries(uds_unix_tunnel rt ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT}) +add_library(uds_unix_tunnel_module SHARED + ${UDS_UNIX_TUNNEL_SOURCES} + ) -target_include_directories(uds_unix_tunnel PRIVATE ./include/) -set_target_properties(uds_unix_tunnel PROPERTIES PREFIX "") -set_target_properties(uds_unix_tunnel PROPERTIES SUFFIX ".sdv") +target_link_libraries(uds_unix_tunnel + PRIVATE + uds_unix_sockets_static + rt + ${CMAKE_DL_LIBS} + ${CMAKE_THREAD_LIBS_INIT} +) + +target_link_libraries(uds_unix_tunnel_module + PRIVATE + uds_unix_sockets_static + rt + ${CMAKE_DL_LIBS} + ${CMAKE_THREAD_LIBS_INIT} +) + +target_include_directories(uds_unix_tunnel + PRIVATE + ./include/ + ../uds_unix_sockets/ +) + +target_include_directories(uds_unix_tunnel_module + PRIVATE + ./include/ + ../uds_unix_sockets/ +) +set_target_properties(uds_unix_tunnel_module PROPERTIES PREFIX "") +set_target_properties(uds_unix_tunnel_module PROPERTIES SUFFIX ".sdv") +set_target_properties(uds_unix_tunnel_module PROPERTIES OUTPUT_NAME "uds_unix_tunnel") # Build dependencies add_dependencies(uds_unix_tunnel CompileCoreIDL) +add_dependencies(uds_unix_tunnel_module CompileCoreIDL) # Appending the service in the service list -set(SDV_Service_List ${SDV_Service_List} uds_unix_tunnel PARENT_SCOPE) +set(SDV_Service_List ${SDV_Service_List} uds_unix_tunnel_module PARENT_SCOPE) endif() \ No newline at end of file diff --git a/sdv_services/uds_unix_tunnel/channel_mgnt.cpp b/sdv_services/uds_unix_tunnel/channel_mgnt.cpp index 97ae63c..d07e215 100644 --- a/sdv_services/uds_unix_tunnel/channel_mgnt.cpp +++ b/sdv_services/uds_unix_tunnel/channel_mgnt.cpp @@ -1,3 +1,15 @@ +/******************************************************************************** +* Copyright (c) 2025-2026 ZF Friedrichshafen AG +* +* This program and the accompanying materials are made available under the +* terms of the Apache License Version 2.0 which is available at +* https://www.apache.org/licenses/LICENSE-2.0 +* +* SPDX-License-Identifier: Apache-2.0 +* +* Contributors: +* Denisa Ros - initial API and implementation +********************************************************************************/ #if defined(__unix__) #include "channel_mgnt.h" @@ -15,6 +27,7 @@ namespace { +static std::atomic g_nextChannelId{1}; /** * @brief Parses a semicolon-separated list of key=value pairs into a map. @@ -52,23 +65,113 @@ static std::string ClampSunPath(const std::string& p) return (p.size() < MaxLen) ? p : p.substr(0, MaxLen - 1); } +static std::string GetUserRuntimeDir() +{ + const std::string path = "/run/ipc/sdv"; + + struct stat st{}; + if (::stat(path.c_str(), &st) == 0) + { + return path; + } + + // fallback if /run/ipc/sdv is not available + const std::string fallback = "/tmp/sdv"; + ::mkdir(fallback.c_str(), 0770); + + return fallback; +} + + +static std::string BuildTunnelPath(const std::string& baseDir, + const std::string& tunnel, + uint32_t instanceId, + uint32_t pid) +{ + std::string dir = baseDir + "/" + tunnel; + + if (::mkdir(dir.c_str(), 0770) != 0 && errno != EEXIST) + { + SDV_LOG_WARNING("Failed to create tunnel directory"); + } + + return dir + "/vapi_" + + std::to_string(instanceId) + "_" + + std::to_string(pid) + ".sock"; +} + +static bool ExtractTunnelPathFromConfig(const std::string& input, std::string& outPath) +{ + const auto kv = ParseKV(input); + if (kv.count("proto")) + { + if (kv.at("proto") != "tunnel") + return false; + + auto it = kv.find("path"); + outPath = (it != kv.end()) ? it->second : std::string{}; + return true; + } + + sdv::toml::CTOMLParser parser(input); + if (!parser.IsValid()) + return false; + + const std::string providerName = parser.GetDirect("Provider.Name").GetValue(); + if (!providerName.empty() && + providerName != "unix_domain_sockets_tunnel" && + providerName != "UnixTunnelChannelControl" && + providerName != "WinTunnelChannelControl") + { + return false; + } + + const std::string nested = parser.GetDirect("Provider.ConnectString").GetValue(); + if (!nested.empty()) + { + return ExtractTunnelPathFromConfig(nested, outPath); + } + + auto pathNode = parser.GetDirect("IpcChannel.Path"); + if (pathNode.GetType() == sdv::toml::ENodeType::node_string) + { + outPath = static_cast(pathNode.GetValue()); + return true; + } + + auto nameNode = parser.GetDirect("IpcChannel.Name"); + if (nameNode.GetType() == sdv::toml::ENodeType::node_string) + { + const std::string name = static_cast(nameNode.GetValue()); + if (!name.empty()) + { + outPath = GetUserRuntimeDir() + "/" + name + ".sock"; + return true; + } + } + + outPath.clear(); + return true; +} + +static std::string ExtractTunnelName(const std::string& input) +{ + const auto kv = ParseKV(input); + if (kv.count("tunnel")) + { + return kv.at("tunnel"); + } + + // Fallback default + return "default"; +} + + } // anonymous namespace std::string CUnixTunnelChannelMgnt::MakeUserRuntimeDir() { - std::ostringstream oss; - oss << "/run/user/" << ::getuid(); - - struct stat st{}; - if (::stat(oss.str().c_str(), &st) == 0) - { - std::string path = oss.str() + "/sdv"; - ::mkdir(path.c_str(), 0770); - return path; - } - - ::mkdir("/tmp/sdv", 0770); - return "/tmp/sdv"; + return GetUserRuntimeDir(); } bool CUnixTunnelChannelMgnt::OnInitialize() @@ -77,9 +180,11 @@ bool CUnixTunnelChannelMgnt::OnInitialize() } void CUnixTunnelChannelMgnt::OnShutdown() +{} + +void CUnixTunnelChannelMgnt::OnDestroy() { - // Actual cleanup is handled by destructors of CUnixTunnelConnection - // and CUnixSocketConnection (shared_ptr). + m_watchdog.Clear(); } sdv::ipc::SChannelEndpoint CUnixTunnelChannelMgnt::CreateEndpoint( @@ -88,91 +193,270 @@ sdv::ipc::SChannelEndpoint CUnixTunnelChannelMgnt::CreateEndpoint( sdv::ipc::SChannelEndpoint endpoint{}; const std::string baseDir = MakeUserRuntimeDir(); - std::string name = "TUNNEL_" + std::to_string(::getpid()); - std::string path = baseDir + "/" + name + ".sock"; + uint32_t instanceId = 1000; + const sdv::app::IAppContext* pCtx = sdv::core::GetCore(); + if (pCtx && pCtx->GetInstanceID() != 0) + { + instanceId = pCtx->GetInstanceID(); + } + + // Extract tunnel name from config + std::string input = static_cast(ssChannelConfig); + std::string tunnel = ExtractTunnelName(input); + + // Build safe path + std::string path = BuildTunnelPath(baseDir, tunnel, instanceId, static_cast(::getpid())); - // Parse optional TOML config for custom name/path if (!ssChannelConfig.empty()) { - sdv::toml::CTOMLParser cfg(ssChannelConfig.c_str()); - auto nameNode = cfg.GetDirect("IpcChannel.Name"); - if (nameNode.GetType() == sdv::toml::ENodeType::node_string) - name = static_cast(nameNode.GetValue()); + std::string configuredPath; - auto pathNode = cfg.GetDirect("IpcChannel.Path"); - if (pathNode.GetType() == sdv::toml::ENodeType::node_string) - path = static_cast(pathNode.GetValue()); + if (ExtractTunnelPathFromConfig(input, configuredPath) && !configuredPath.empty()) + { + std::string baseName = configuredPath; + auto pos = baseName.find_last_of('/'); + if (pos != std::string::npos) + { + baseName = baseName.substr(pos + 1); + } + path = baseDir + "/" + tunnel + "/" + baseName; + SDV_LOG_WARNING("Using custom path without tunnel isolation"); + } else - path = baseDir + "/" + name + ".sock"; + { + // fallback - still use tunnel-based path + tunnel = ExtractTunnelName(input); + instanceId = 1000; + + if (pCtx && pCtx->GetInstanceID() != 0) + { + instanceId = pCtx->GetInstanceID(); + } + + path = BuildTunnelPath(baseDir, tunnel, instanceId, static_cast(::getpid())); + } } path = ClampSunPath(path); // Create underlying UDS server transport - auto udsServer = std::make_shared( - -1, - /*acceptConnectionRequired*/ true, - path); + auto udsServer = std::make_shared(-1, /*acceptConnectionRequired*/ true, path); + uint32_t chId = g_nextChannelId++; // Create tunnel wrapper on top of UDS - auto tunnelServer = std::make_shared( - udsServer, - /*channelId*/ 0u); + auto tunnelServer = std::make_shared(udsServer, /*channelId*/ chId); - m_ServerTunnels.push_back(tunnelServer); + // Ignore cppcheck warning; if construction failed, an exception is expected first. + // cppcheck-suppress knownConditionTrueFalse + if (!tunnelServer) + return {}; + + tunnelServer->SetWatchDogRemoveCallback([this](const void* connection) + { + m_watchdog.RemoveConnection(connection); + }); + m_watchdog.AddConnection(tunnelServer); endpoint.pConnection = static_cast(tunnelServer.get()); - endpoint.ssConnectString = "proto=tunnel;role=server;path=" + path + ";"; + const std::string tunnelConnectString = "proto=tunnel;path=" + path + ";tunnel=" + tunnel + ";"; + endpoint.ssConnectString = std::string("[Provider]\n") + + "Name = \"unix_domain_sockets_tunnel\"\n" + + "ConnectString = \"" + tunnelConnectString + "\"\n"; return endpoint; } -sdv::IInterfaceAccess* CUnixTunnelChannelMgnt::Access( - const sdv::u8string& ssConnectString) +sdv::IInterfaceAccess* CUnixTunnelChannelMgnt::Access(const sdv::u8string& ssConnectString) { - const auto kv = ParseKV(static_cast(ssConnectString)); + const std::string input = static_cast(ssConnectString); + std::string tunnel = ExtractTunnelName(input); + bool parsed = false; + bool isServer = false; + std::string path; - // Only handle proto=tunnel - if (!kv.count("proto") || kv.at("proto") != "tunnel") + // Parse structured TOML forms first; raw connect strings are handled below. + if (input.rfind("proto=tunnel", 0) != 0) + { + + sdv::toml::CTOMLParser parser(input); + if (!parser.IsValid()) + { + SDV_LOG_WARNING("[TUNNEL][Access] TOML parse failed. Trying text fallback."); + + // Fallback for malformed TOML: try to recover an embedded proto=tunnel connect string. + const auto protoPos = input.find("proto=tunnel"); + if (protoPos != std::string::npos) + { + std::string fallbackCs = input.substr(protoPos); + + const auto quotePos = fallbackCs.find('"'); + if (quotePos != std::string::npos) + fallbackCs = fallbackCs.substr(0, quotePos); + + const auto newlinePos = fallbackCs.find('\n'); + if (newlinePos != std::string::npos) + fallbackCs = fallbackCs.substr(0, newlinePos); + + const auto kv = ParseKV(fallbackCs); + if (kv.count("proto") && kv.at("proto") == "tunnel") + { + parsed = true; + isServer = (kv.count("role") && kv.at("role") == "server"); + if (kv.count("path")) + path = kv.at("path"); + } + } + + if (!parsed) + { + SDV_LOG_WARNING("[TUNNEL][Access] Fallback parse failed. Returning nullptr"); + return nullptr; + } + } + + if (!parsed) + { + const std::string providerName = parser.GetDirect("Provider.Name").GetValue(); + if (!providerName.empty()) + { + if (providerName != "unix_domain_sockets_tunnel" && + providerName != "UnixTunnelChannelControl" && + providerName != "WinTunnelChannelControl") + { + SDV_LOG_WARNING("[TUNNEL][Access] Unsupported provider. Returning nullptr"); + return nullptr; + } + + const std::string nested = parser.GetDirect("Provider.ConnectString").GetValue(); + if (!nested.empty()) + { + const auto nestedKv = ParseKV(nested); + if (nestedKv.count("tunnel")) + { + tunnel = nestedKv.at("tunnel"); + } + + if (nestedKv.count("proto") && nestedKv.at("proto") != "tunnel") + { + SDV_LOG_WARNING("[TUNNEL][Access] Nested proto is not tunnel. Returning nullptr"); + return nullptr; + } + + if (nestedKv.count("path")) + { + std::string baseName = nestedKv.at("path"); + auto pos = baseName.find_last_of('/'); + if (pos != std::string::npos) + { + baseName = baseName.substr(pos + 1); + } + + path = MakeUserRuntimeDir() + "/" + tunnel + "/" + baseName; + } + } + + parsed = true; + isServer = false; + } + else + { + // Callers can pass [IpcChannel] directly to Access(). + parsed = true; + isServer = false; + } + + if (path.empty()) + { + auto pathNode = parser.GetDirect("IpcChannel.Path"); + if (pathNode.GetType() == sdv::toml::ENodeType::node_string) + { + std::string baseName = static_cast(pathNode.GetValue()); + + auto pos = baseName.find_last_of('/'); + if (pos != std::string::npos) + { + baseName = baseName.substr(pos + 1); + } + + path = MakeUserRuntimeDir() + "/" + tunnel + "/" + baseName; + } + else + { + auto nameNode = parser.GetDirect("IpcChannel.Name"); + if (nameNode.GetType() == sdv::toml::ENodeType::node_string) + { + const std::string name = static_cast(nameNode.GetValue()); + if (!name.empty()) + { + path = MakeUserRuntimeDir() + "/" + tunnel + "/" + name + ".sock"; + } + } + } + } + } + } + + // Raw KV fallback: proto=tunnel;role=...;path=...; + if (!parsed) + { + if (input.rfind("proto=tunnel", 0) != 0) + { + SDV_LOG_WARNING("[TUNNEL][Access] Raw KV parse rejected: input does not start with proto=tunnel. Returning nullptr"); + return nullptr; + } + + const auto kv = ParseKV(input); + if (!kv.count("proto") || kv.at("proto") != "tunnel") + { + SDV_LOG_WARNING("[TUNNEL][Access] Raw KV parse rejected: missing/invalid proto. Returning nullptr "); + return nullptr; + } + + parsed = true; + isServer = (kv.count("role") && kv.at("role") == "server"); + if (kv.count("path")) + { + path = kv.at("path"); + } + } + + if (!parsed) { return nullptr; } - const bool isServer = - (kv.count("role") && kv.at("role") == "server"); - - const std::string path = - kv.count("path") - ? kv.at("path") - : (MakeUserRuntimeDir() + "/TUNNEL_auto.sock"); - - if (isServer) + if (path.empty()) { - // For simplicity, create a new server tunnel instance for each Access(). - // The SDV framework is expected to call Access(serverCS) only once in normal cases. - auto udsServer = std::make_shared( - -1, - /*acceptConnectionRequired*/ true, - path); + tunnel = ExtractTunnelName(input); - auto tunnelServer = std::make_shared( - udsServer, - /*channelId*/ 0u); + uint32_t instanceId = 1000; + const sdv::app::IAppContext* pCtx = sdv::core::GetCore(); + if (pCtx && pCtx->GetInstanceID() != 0) + { + instanceId = pCtx->GetInstanceID(); + } - m_ServerTunnels.push_back(tunnelServer); - return static_cast(tunnelServer.get()); + path = BuildTunnelPath(MakeUserRuntimeDir(), tunnel, instanceId, static_cast(::getpid())); } - // Client: allocate raw pointer (expected to be managed by SDV framework via IObjectDestroy) - auto udsClient = std::make_shared( - -1, - /*acceptConnectionRequired*/ false, - path); + path = ClampSunPath(path); - auto* tunnelClient = - new CUnixTunnelConnection(udsClient, /*channelId*/ 0u); + std::shared_ptr transport = std::make_shared(-1, /*acceptConnectionRequired*/ isServer, path); + uint32_t chId = g_nextChannelId++; + std::shared_ptr connection = std::make_shared(transport, /*channelId*/ chId); - return static_cast(tunnelClient); + // Ignore cppcheck warning; if construction failed, an exception is expected first. + // cppcheck-suppress knownConditionTrueFalse + if (!connection) + return nullptr; + + connection->SetWatchDogRemoveCallback([this](const void* instance) + { + m_watchdog.RemoveConnection(instance); + }); + + m_watchdog.AddConnection(connection); + return static_cast(connection.get()); } #endif // defined(__unix__) \ No newline at end of file diff --git a/sdv_services/uds_unix_tunnel/channel_mgnt.h b/sdv_services/uds_unix_tunnel/channel_mgnt.h index 09484c5..8761ac0 100644 --- a/sdv_services/uds_unix_tunnel/channel_mgnt.h +++ b/sdv_services/uds_unix_tunnel/channel_mgnt.h @@ -16,43 +16,11 @@ #include #include -#include "../sdv_services/uds_unix_sockets/channel_mgnt.h" // existing UDS transport +#include "watchdog.h" +#include class CUnixTunnelConnection; -/** - * @brief Initialize WinSock on Windows (idempotent). - * - * This helper ensures WSAStartup() is called only once in the process. - * On non-Windows platforms, this is a no-op and always returns success. - * - * @return 0 on success, otherwise a WinSock error code (Windows only). - */ -inline int StartUpWinSock() -{ -#ifdef _WIN32 - static bool isInitialized = false; - if (isInitialized) - { - return 0; - } - WSADATA wsaData {}; - const int error = WSAStartup(MAKEWORD(2, 2), &wsaData); - if (error != 0) - { - SDV_LOG_ERROR("WSAStartup failed with error: ", std::to_string(error)); - } - else - { - SDV_LOG_INFO("WSAStartup initialized"); - isInitialized = true; - } - return error; -#else - // Non-Windows: nothing to do. Return success for symmetry - return 0; -#endif -} /** * @class CUnixTunnelChannelMgnt * @brief IPC channel management class for Unix Domain Socket tunnel communication. @@ -76,8 +44,8 @@ public: // Object declarations DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::system_object) DECLARE_OBJECT_CLASS_NAME("UnixTunnelChannelControl") - DECLARE_OBJECT_CLASS_ALIAS("TunnelChannelControl") - DECLARE_DEFAULT_OBJECT_NAME("TunnelChannelControl") + DECLARE_OBJECT_CLASS_ALIAS("unix_domain_sockets_tunnel") + DECLARE_DEFAULT_OBJECT_NAME("unix_domain_sockets_tunnel") DECLARE_OBJECT_SINGLETON() /** @@ -96,6 +64,11 @@ public: */ virtual void OnShutdown() override; + /** + * @brief Last function called before destruction. Overload of sdv::CSdvObject::OnDestroy. + */ + virtual void OnDestroy() override; + /** * @brief Creates a tunnel endpoint (server side) and returns endpoint info. * @@ -126,12 +99,7 @@ private: */ static std::string MakeUserRuntimeDir(); - /** - * @brief Keeps server-side tunnel connections alive for the lifetime of the manager. - * - * This ensures that server tunnel objects are not destroyed while the manager is active. - */ - std::vector> m_ServerTunnels; + CUnixTunnelConnectionWatchDog m_watchdog; }; DEFINE_SDV_OBJECT(CUnixTunnelChannelMgnt) diff --git a/sdv_services/uds_unix_tunnel/connection.cpp b/sdv_services/uds_unix_tunnel/connection.cpp index 4d274fc..c89f879 100644 --- a/sdv_services/uds_unix_tunnel/connection.cpp +++ b/sdv_services/uds_unix_tunnel/connection.cpp @@ -28,6 +28,12 @@ CUnixTunnelConnection::CUnixTunnelConnection( // No additional initialization required; acts as a thin wrapper. } +void CUnixTunnelConnection::SetWatchDogRemoveCallback(std::function callback) +{ + std::lock_guard lock(m_WatchdogMtx); + m_WatchdogRemoveCallback = std::move(callback); +} + /** * @brief Prepends a tunnel header and forwards the data to the underlying transport. @@ -155,11 +161,38 @@ sdv::ipc::EConnectState CUnixTunnelConnection::GetConnectState() const void CUnixTunnelConnection::DestroyObject() { + bool expected = false; + if (!m_DestroyObjectCalled.compare_exchange_strong(expected, true)) + { + return; + } + + // Stop forwarding to upper layer before transport teardown. + { + std::lock_guard lock(m_CallbackMtx); + m_pUpperReceiver = nullptr; + m_pUpperEvent = nullptr; + } + + SetConnectState(sdv::ipc::EConnectState::terminating); + // Disconnect underlying transport and clear callbacks. Disconnect(); - std::lock_guard lock(m_CallbackMtx); - m_Transport.reset(); + { + std::lock_guard lock(m_CallbackMtx); + m_Transport.reset(); + } + + std::function removeCallback; + { + std::lock_guard watchdogLock(m_WatchdogMtx); + removeCallback = std::move(m_WatchdogRemoveCallback); + } + if (removeCallback) + { + removeCallback(this); + } } void CUnixTunnelConnection::ReceiveData(/*inout*/ sdv::sequence>& seqData) diff --git a/sdv_services/uds_unix_tunnel/connection.h b/sdv_services/uds_unix_tunnel/connection.h index 7d30ccb..5ebad48 100644 --- a/sdv_services/uds_unix_tunnel/connection.h +++ b/sdv_services/uds_unix_tunnel/connection.h @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -179,12 +180,20 @@ public: */ uint16_t GetChannelId() const noexcept { return m_ChannelId; } + /** + * @brief Register callback used to remove this connection from manager watchdog storage. + */ + void SetWatchDogRemoveCallback(std::function callback); + private: std::shared_ptr m_Transport; ///< shared physical tunnel port uint16_t m_ChannelId {0}; ///< default logical channel id sdv::ipc::IDataReceiveCallback* m_pUpperReceiver {nullptr}; ///< Callback to upper layer (data receive) sdv::ipc::IConnectEventCallback* m_pUpperEvent {nullptr}; ///< Callback to upper layer (state event) mutable std::mutex m_CallbackMtx; ///< Mutex to guard callback access + std::mutex m_WatchdogMtx; + std::function m_WatchdogRemoveCallback; + std::atomic m_DestroyObjectCalled { false }; }; #endif // UNIX_SOCKET_TUNNEL_CONNECTION_H diff --git a/sdv_services/uds_unix_tunnel/watchdog.cpp b/sdv_services/uds_unix_tunnel/watchdog.cpp new file mode 100644 index 0000000..e2dcf8f --- /dev/null +++ b/sdv_services/uds_unix_tunnel/watchdog.cpp @@ -0,0 +1,58 @@ +/******************************************************************************** +* Copyright (c) 2025-2026 ZF Friedrichshafen AG +* +* This program and the accompanying materials are made available under the +* terms of the Apache License Version 2.0 which is available at +* https://www.apache.org/licenses/LICENSE-2.0 +* +* SPDX-License-Identifier: Apache-2.0 +* +* Contributors: +* Denisa Ros - initial API and implementation +********************************************************************************/ + +#if defined(__unix__) + +#include "watchdog.h" + +void CUnixTunnelConnectionWatchDog::AddConnectionImpl(const std::shared_ptr& connection) +{ + std::lock_guard lock(m_Mutex); + m_Connections[connection.get()] = connection; +} + +void CUnixTunnelConnectionWatchDog::RemoveConnection(const void* connection) +{ + if (!connection) + { + return; + } + + std::shared_ptr removed; + { + std::lock_guard lock(m_Mutex); + auto it = m_Connections.find(connection); + if (it == m_Connections.end()) + { + return; + } + + removed = std::move(it->second); + m_Connections.erase(it); + } + + removed.reset(); +} + +void CUnixTunnelConnectionWatchDog::Clear() +{ + std::map> localConnections; + { + std::lock_guard lock(m_Mutex); + localConnections.swap(m_Connections); + } + + localConnections.clear(); +} + +#endif // defined(__unix__) diff --git a/sdv_services/uds_unix_tunnel/watchdog.h b/sdv_services/uds_unix_tunnel/watchdog.h new file mode 100644 index 0000000..1fa0fb9 --- /dev/null +++ b/sdv_services/uds_unix_tunnel/watchdog.h @@ -0,0 +1,47 @@ +/******************************************************************************** +* Copyright (c) 2025-2026 ZF Friedrichshafen AG +* +* This program and the accompanying materials are made available under the +* terms of the Apache License Version 2.0 which is available at +* https://www.apache.org/licenses/LICENSE-2.0 +* +* SPDX-License-Identifier: Apache-2.0 +* +* Contributors: +* Denisa Ros - initial API and implementation +********************************************************************************/ + +#if defined(__unix__) +#ifndef UDS_UNIX_TUNNEL_WATCHDOG_H +#define UDS_UNIX_TUNNEL_WATCHDOG_H + +#include +#include +#include + +class CUnixTunnelConnectionWatchDog +{ +public: + template + void AddConnection(const std::shared_ptr& connection) + { + if (!connection) + { + return; + } + + AddConnectionImpl(std::static_pointer_cast(connection)); + } + + void RemoveConnection(const void* connection); + void Clear(); + +private: + void AddConnectionImpl(const std::shared_ptr& connection); + + std::mutex m_Mutex; + std::map> m_Connections; +}; + +#endif // UDS_UNIX_TUNNEL_WATCHDOG_H +#endif // defined(__unix__) diff --git a/sdv_services/uds_win_sockets/CMakeLists.txt b/sdv_services/uds_win_sockets/CMakeLists.txt index ac138e3..2b4e9e9 100644 --- a/sdv_services/uds_win_sockets/CMakeLists.txt +++ b/sdv_services/uds_win_sockets/CMakeLists.txt @@ -12,31 +12,59 @@ if(WIN32) # Define project project(uds_win_sockets VERSION 1.0 LANGUAGES CXX) -# Define target -add_library(uds_win_sockets STATIC +# Build sources once and expose them in two forms: +# - uds_win_sockets: static link target for tests / other local libraries on MinGW +# - uds_win_sockets_module: runtime-loadable .sdv module for ModuleControl +set(UDS_WIN_SOCKETS_SOURCES channel_mgnt.cpp connection.cpp + watchdog.cpp ) -target_include_directories(uds_win_sockets - PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR} - ./include/ +add_library(uds_win_sockets STATIC + ${UDS_WIN_SOCKETS_SOURCES} +) + +add_library(uds_win_sockets_module SHARED + ${UDS_WIN_SOCKETS_SOURCES} +) + +target_include_directories(uds_win_sockets + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ./include/ +) + +target_include_directories(uds_win_sockets_module + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ./include/ ) target_link_libraries(uds_win_sockets PUBLIC - ${CMAKE_THREAD_LIBS_INIT} + ${CMAKE_THREAD_LIBS_INIT} Ws2_32.lib ) -set_target_properties(uds_win_sockets PROPERTIES PREFIX "") -set_target_properties(uds_win_sockets PROPERTIES SUFFIX ".sdv") +target_link_libraries(uds_win_sockets_module + PUBLIC + ${CMAKE_THREAD_LIBS_INIT} + Ws2_32.lib +) + +set_target_properties(uds_win_sockets_module PROPERTIES + PREFIX "" + SUFFIX ".sdv" + OUTPUT_NAME "uds_win_sockets" + ARCHIVE_OUTPUT_NAME "uds_win_sockets_module" +) # Build dependencies add_dependencies(uds_win_sockets CompileCoreIDL) +add_dependencies(uds_win_sockets_module CompileCoreIDL) # Appending the service in the service list -set(SDV_Service_List ${SDV_Service_List} uds_win_sockets PARENT_SCOPE) +set(SDV_Service_List ${SDV_Service_List} uds_win_sockets_module PARENT_SCOPE) endif() diff --git a/sdv_services/uds_win_sockets/channel_mgnt.cpp b/sdv_services/uds_win_sockets/channel_mgnt.cpp index 6e42b9c..6c5fe05 100644 --- a/sdv_services/uds_win_sockets/channel_mgnt.cpp +++ b/sdv_services/uds_win_sockets/channel_mgnt.cpp @@ -11,15 +11,20 @@ * Denisa Ros - initial API and implementation ********************************************************************************/ #ifdef _WIN32 - #include "channel_mgnt.h" #include "connection.h" -#include "../../global/base64.h" -#include -#include - +#include #include +#include +#include +#include + +#include "../../global/base64.h" +#include +#include +#include +#include #pragma push_macro("interface") #undef interface @@ -44,53 +49,23 @@ namespace { -/** - * @brief Parse a UDS connect/config string and extract the path - * - * Expected format (substring-based, not strict): - * "proto=uds;path=;" - * - * Behavior: - * - If "proto=uds" is missing -> returns false (not a UDS config) - * - If "path=" is missing -> returns true and outPath is cleared - * - If "path=" is present -> extracts the substring until ';' or end - * - * @param cs Input configuration / connect string - * @param outPath Output: extracted path (possibly empty) - * @return true if this looks like a UDS string, false otherwise - */ -static bool ParseUdsPath(const std::string& cs, std::string& outPath) +static bool EnsureWSAInitialized() { - constexpr const char* protoKey = "proto=uds"; - constexpr const char* pathKey = "path="; + static std::once_flag s_once; + static bool s_ok = false; - // Must contain "proto=uds" to be considered UDS - if (cs.find(protoKey) == std::string::npos) + std::call_once(s_once, []() { - return false; - } + WSADATA wsa{}; + const int rc = WSAStartup(MAKEWORD(2, 2), &wsa); + s_ok = (rc == 0); + if (!s_ok) + { + SDV_LOG_ERROR("[AF_UNIX] WSAStartup failed, rc=", rc); + } + }); - const auto p = cs.find(pathKey); - if (p == std::string::npos) - { - // No path given, but proto=uds is present -> use default later - outPath.clear(); - return true; - } - - const auto start = p + std::strlen(pathKey); - const auto end = cs.find(';', start); - - if (end == std::string::npos) - { - outPath = cs.substr(start); - } - else - { - outPath = cs.substr(start, end - start); - } - - return true; + return s_ok; } /** @@ -105,6 +80,7 @@ static bool ParseUdsPath(const std::string& cs, std::string& outPath) * * @return Expanded string, or the original input on failure */ + static std::string ExpandEnvVars(const std::string& in) { if (in.find('%') == std::string::npos) @@ -112,9 +88,8 @@ static std::string ExpandEnvVars(const std::string& in) return in; } - char buf[4096] = {}; - DWORD n = ExpandEnvironmentStringsA(in.c_str(), buf, static_cast(sizeof(buf))); - + char buf[4096] = {}; + DWORD n = ExpandEnvironmentStringsA(in.c_str(), buf, static_cast(sizeof(buf))); if (n > 0 && n < sizeof(buf)) { return std::string(buf); @@ -135,19 +110,181 @@ static std::string ExpandEnvVars(const std::string& in) * * @return A safe pathname guaranteed to fit into sun_path */ + static std::string ClampUdsPath(const std::string& p) { - SOCKADDR_UN tmp{}; + SOCKADDR_UN tmp{}; constexpr auto kMax = sizeof(tmp.sun_path) - 1; - if (p.size() <= kMax) { return p; } - return p.substr(0, kMax); } +/*static std::string BuildFinalUdsPath(const std::string& rawPath) +{ + std::string full = ExpandEnvVars(rawPath); + + auto pos = full.find_last_of("\\/"); + if (pos != std::string::npos) + { + const std::string dir = full.substr(0, pos); + CreateDirectoryA(dir.c_str(), nullptr); + } + + return ClampUdsPath(full); +}*/ + + + +/** + * @brief Parse a UDS connect/config string and extract the path + * + * Expected format (substring-based, not strict): + * "proto=uds;path=;" + * + * Behavior: + * - If "proto=uds" is missing -> returns false (not a UDS config) + * - If "path=" is missing -> returns true and outPath is cleared + * - If "path=" is present -> extracts the substring until ';' or end + * + * @param cs Input configuration / connect string + * @param outPath Output: extracted path (possibly empty) + * @return true if this looks like a UDS string, false otherwise + */ +static bool ParseUdsPath(const std::string& cs, std::string& outPath) +{ + constexpr const char* protoKey = "proto=uds"; + constexpr const char* pathKey = "path="; + + // Strict raw connect string only + if (cs.rfind(protoKey, 0) != 0) + { + return false; + } + + const auto p = cs.find(pathKey); + if (p == std::string::npos) + { + outPath.clear(); + return true; + } + + const auto start = p + std::strlen(pathKey); + const auto end = cs.find(';', start); + if (end == std::string::npos) + { + outPath = cs.substr(start); + } + else + { + outPath = cs.substr(start, end - start); + } + + return true; +} + +static std::string SanitizeUdsName(std::string name) +{ + for (char& ch : name) + { + const bool isAlphaNum = (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9'); + if (!isAlphaNum && ch != '_' && ch != '-') + { + ch = '_'; + } + } + + if (name.empty()) + { + name = "sdv"; + } + + return name; +} + +/** + * @brief Build a named UDS raw path using the channel name + * + * @param[in] channelName The name of the channel + * + * @return A raw UDS path suitable for further processing + */ +static std::string BuildNamedUdsRawPath(const std::string& channelName) +{ + return "%LOCALAPPDATA%/sdv/" + SanitizeUdsName(channelName) + ".sock"; +} + +/** + * @brief Extract a UDS connect string from either raw UDS format or Provider TOML. + * + * Supported inputs: + * - "proto=uds;path=<...>;" + * - [Provider]\nName="unix_domain_sockets"\nConnectString="proto=uds;path=<...>;" + */ +static bool ExtractUdsConnectString(const std::string& in, + std::string& outUdsConnectString) +{ + std::string path; + + // Case 1: strict raw UDS connect string + if (ParseUdsPath(in, path)) + { + outUdsConnectString = in; + return true; + } + + // Do not run the TOML parser on obviously non-TOML garbage + //if (!LooksLikeToml(in)) + //{ + // return false; + //} + + // Case 2: structured TOML + sdv::toml::CTOMLParser parser(in); + if (!parser.IsValid()) + { + return false; + } + + const std::string providerName = parser.GetDirect("Provider.Name").GetValue(); + if (!providerName.empty() && + providerName != "unix_domain_sockets" && + providerName != "WinSocketsChannelControl") + { + return false; + } + + const std::string nested = parser.GetDirect("Provider.ConnectString").GetValue(); + if (!nested.empty()) + { + if (ParseUdsPath(nested, path)) + { + outUdsConnectString = nested; + return true; + } + return false; + } + + const std::string cfgPath = parser.GetDirect("IpcChannel.Path").GetValue(); + if (!cfgPath.empty()) + { + outUdsConnectString = "proto=uds;path=" + cfgPath + ";"; + return true; + } + + const std::string cfgName = parser.GetDirect("IpcChannel.Name").GetValue(); + if (!cfgName.empty()) + { + outUdsConnectString = "proto=uds;path=" + BuildNamedUdsRawPath(cfgName) + ";"; + return true; + } + + return false; +} + + /** * @brief Normalize a UDS path for display/logging purposes * @@ -166,20 +303,18 @@ static std::string ClampUdsPath(const std::string& p) static std::string NormalizeUdsPathForWindows(const std::string& raw) { std::string p = ExpandEnvVars(raw); - - const size_t pos = p.find_last_of("/\\"); - std::string base = (pos == std::string::npos) ? p : p.substr(pos + 1); - + const size_t pos = p.find_last_of("/\\"); + std::string base = (pos == std::string::npos) ? p : p.substr(pos + 1); if (base.empty()) { base = "sdv.sock"; } SDV_LOG_INFO("[AF_UNIX] Normalize raw='", raw, "' -> base='", base, "'"); - return ClampUdsPath(base); } + /** * @brief Build a short absolute Win32 path suitable for AF_UNIX `sun_path` * @@ -224,6 +359,41 @@ static std::string MakeShortWinUdsPath(const std::string& raw) return ClampUdsPath(full); } +/** + * @brief Build the default UDS path using the current SDV instance ID. + * + * Using a single global socket filename makes independently running test + * processes trample each other when the build executes multiple post-build + * tests in parallel. Namespace the implicit endpoint per instance instead. + */ +static std::string GetDefaultUdsRawPath() +{ + uint32_t instanceId = 1000u; + const sdv::app::IAppContext* pAppContext = sdv::core::GetCore(); + if (pAppContext && pAppContext->GetInstanceID() != 0u) + { + instanceId = pAppContext->GetInstanceID(); + } + + return "%LOCALAPPDATA%/sdv/vapi_" + std::to_string(instanceId) + ".sock"; +} + +static std::string GetUniqueEndpointUdsRawPath() +{ + static std::atomic nextEndpointId { 0u }; + + uint32_t instanceId = 1000u; + const sdv::app::IAppContext* pAppContext = sdv::core::GetCore(); + if (pAppContext && pAppContext->GetInstanceID() != 0u) + { + instanceId = pAppContext->GetInstanceID(); + } + + const uint32_t endpointId = nextEndpointId.fetch_add(1u, std::memory_order_relaxed); + return "%LOCALAPPDATA%/sdv/vapi_" + std::to_string(instanceId) + "_" + + std::to_string(static_cast(GetCurrentProcessId())) + "_" + std::to_string(endpointId) + ".sock"; +} + /** * @brief Create a listening AF_UNIX socket on Windows * @@ -239,6 +409,9 @@ static std::string MakeShortWinUdsPath(const std::string& raw) */ static SOCKET CreateUnixListenSocket(const std::string& rawPath) { + if (!EnsureWSAInitialized()) + return INVALID_SOCKET; + SOCKET s = socket(AF_UNIX, SOCK_STREAM, 0); if (s == INVALID_SOCKET) { @@ -285,118 +458,99 @@ static SOCKET CreateUnixListenSocket(const std::string& rawPath) return s; } -/** - * @brief Connect to a Windows AF_UNIX server socket with retry logic - * - * Repeatedly attempts to connect to the server's UDS path until either: - * - connection succeeds, or - * - total timeout is exceeded - * - * On each attempt: - * - a new socket() is created - * - connect() is attempted - * - on failure the socket is closed and retried - * - * This mirrors Linux AF_UNIX behavior where the client waits for the - * server's socket file to appear/become ready - * - * @param[in] rawPath Raw UDS path from configuration - * @param[in] totalTimeoutMs Maximum total wait time in milliseconds - * @param[in] retryDelayMs Delay between retries in milliseconds - * - * @return Connected SOCKET on success, INVALID_SOCKET on timeout or error - */ -static SOCKET ConnectUnixSocket( - const std::string& rawPath, - uint32_t totalTimeoutMs, - uint32_t retryDelayMs) +/*static SOCKET ConnectUnixSocket(const std::string& rawPath, + uint32_t totalTimeoutMs, + uint32_t retryDelayMs) { - const std::string udsPath = MakeShortWinUdsPath(rawPath); + if (!EnsureWSAInitialized()) + { + return INVALID_SOCKET; + } + + const std::string udsPath = BuildFinalUdsPath(rawPath); SOCKADDR_UN addr{}; addr.sun_family = AF_UNIX; strcpy_s(addr.sun_path, sizeof(addr.sun_path), udsPath.c_str()); const int addrlen = static_cast( - offsetof(SOCKADDR_UN, sun_path) + std::strlen(addr.sun_path) + 1 - ); + offsetof(SOCKADDR_UN, sun_path) + std::strlen(addr.sun_path) + 1); - const auto deadline = std::chrono::steady_clock::now() + - std::chrono::milliseconds(totalTimeoutMs); + auto deadline = + std::chrono::steady_clock::now() + std::chrono::milliseconds(totalTimeoutMs); while (true) { SOCKET s = socket(AF_UNIX, SOCK_STREAM, 0); if (s == INVALID_SOCKET) { - SDV_LOG_ERROR("[AF_UNIX] socket() FAIL (client), WSA=", WSAGetLastError()); + SDV_LOG_ERROR("[AF_UNIX] socket FAIL (client), WSA=", WSAGetLastError()); return INVALID_SOCKET; } if (connect(s, reinterpret_cast(&addr), addrlen) == 0) { - SDV_LOG_INFO("[AF_UNIX] connect OK (pathname), path='", udsPath, "'"); + SDV_LOG_INFO("[AF_UNIX] connect OK: ", udsPath); return s; } - int lastError = WSAGetLastError(); + const int err = WSAGetLastError(); closesocket(s); if (std::chrono::steady_clock::now() >= deadline) { - SDV_LOG_ERROR( - "[AF_UNIX] connect TIMEOUT (pathname), last WSA=", - lastError, ", path='", udsPath, "'" - ); + SDV_LOG_ERROR("[AF_UNIX] connect TIMEOUT, WSA=", err, ", path=", udsPath); return INVALID_SOCKET; } std::this_thread::sleep_for(std::chrono::milliseconds(retryDelayMs)); } -} +}*/ } // anonymous namespace bool CSocketsChannelMgnt::OnInitialize() { - return true; -} - -void CSocketsChannelMgnt::OnServerClosed(const std::string& udsPath, CWinsockConnection* ptr) -{ - std::lock_guard lock(m_udsMtx); - - auto it = m_udsServers.find(udsPath); - if (it != m_udsServers.end() && it->second.get() == ptr) - { - // Remove the server entry only if it matches the pointer we know - m_udsServers.erase(it); - } - - // Mark this UDS path as no longer claimed - m_udsServerClaimed.erase(udsPath); + return EnsureWSAInitialized(); } void CSocketsChannelMgnt::OnShutdown() -{} +{ +} + +void CSocketsChannelMgnt::OnDestroy() +{ + m_watchdog.Clear(); +} sdv::ipc::SChannelEndpoint CSocketsChannelMgnt::CreateEndpoint(const sdv::u8string& cfgStr) { - // Ensure WinSock is initialized on Windows - if (StartUpWinSock() != 0) - { - // If WinSock cannot be initialized, we cannot create an endpoint - return {}; - } - // Parse UDS path from config. If proto!=uds, we still default to UDS std::string udsRaw; bool udsRequested = ParseUdsPath(cfgStr, udsRaw); if (!udsRequested || udsRaw.empty()) { - // Default path if not provided or not UDS-specific - udsRaw = "%LOCALAPPDATA%/sdv/vapi.sock"; + sdv::toml::CTOMLParser parser(cfgStr); + const std::string cfgPath = parser.GetDirect("IpcChannel.Path").GetValue(); + const std::string cfgName = parser.GetDirect("IpcChannel.Name").GetValue(); + + if (!cfgPath.empty()) + { + udsRaw = cfgPath; + } + else if (!cfgName.empty()) + { + udsRaw = BuildNamedUdsRawPath(cfgName); + } + else if (cfgStr.empty()) + { + udsRaw = GetUniqueEndpointUdsRawPath(); + } + else + { + udsRaw = GetDefaultUdsRawPath(); + } } std::string udsPath = NormalizeUdsPathForWindows(udsRaw); @@ -411,75 +565,81 @@ sdv::ipc::SChannelEndpoint CSocketsChannelMgnt::CreateEndpoint(const sdv::u8stri // Server-side CWinsockConnection, it will accept() a client on first use auto server = std::make_shared(listenSocket, /*acceptRequired*/ true); - + // Ignore cppcheck warning; if construction failed, an exception is expected first. + // cppcheck-suppress knownConditionTrueFalse + if (!server) { - std::lock_guard lock(m_udsMtx); - m_udsServers[udsPath] = server; - m_udsServerClaimed.erase(udsPath); + return {}; } + server->SetWatchDogRemoveCallback([this](const void* connection) + { + m_watchdog.RemoveConnection(connection); + }); + m_watchdog.AddConnection(server); sdv::ipc::SChannelEndpoint ep{}; - ep.pConnection = static_cast(server.get()); - ep.ssConnectString = "proto=uds;path=" + udsPath + ";"; + ep.pConnection = static_cast(server.get()); + + // Keep compatibility with CCommunicationControl::CreateClientConnection, + // which expects a Provider TOML section with Provider.Name. + const std::string udsConnectString = "proto=uds;path=" + udsPath + ";"; + ep.ssConnectString = udsConnectString; return ep; } sdv::IInterfaceAccess* CSocketsChannelMgnt::Access(const sdv::u8string& cs) { - // Ensure WinSock is initialized - if (StartUpWinSock() != 0) + std::string udsConnectString; + if (!ExtractUdsConnectString(cs, udsConnectString)) { + // Not a UDS connect string / provider description return nullptr; } std::string udsRaw; - if (!ParseUdsPath(cs, udsRaw)) - { - // Not a UDS connect string - return nullptr; - } + ParseUdsPath(udsConnectString, udsRaw); if (udsRaw.empty()) { - udsRaw = "%LOCALAPPDATA%/sdv/vapi.sock"; + udsRaw = GetDefaultUdsRawPath(); } std::string udsPath = NormalizeUdsPathForWindows(udsRaw); SDV_LOG_INFO("[AF_UNIX] Access udsPath=", udsPath); + const bool isServer = (udsConnectString.find("role=server") != std::string::npos); + std::shared_ptr connection; + if (isServer) { - std::lock_guard lock(m_udsMtx); - - auto it = m_udsServers.find(udsPath); - if (it != m_udsServers.end() && it->second != nullptr) + SOCKET listenSocket = CreateUnixListenSocket(udsPath); + if (listenSocket == INVALID_SOCKET) { - // Return the server-side object only once for this UDS path - if (!m_udsServerClaimed.count(udsPath)) - { - m_udsServerClaimed.insert(udsPath); - SDV_LOG_INFO("[AF_UNIX] Access -> RETURN SERVER for ", udsPath); - return it->second.get(); // server object (acceptRequired=true) - } - // Otherwise, later calls will create a client socket below + return nullptr; } + connection = std::make_shared(listenSocket, /*acceptRequired*/ true); + } + else + { + // Client-side endpoint object. The actual socket connect is deferred to AsyncConnect, + // matching the semantic contract used by ipc_com and shared memory. + connection = std::make_shared(udsPath); } - // CLIENT: create a socket connected to the same udsPath - SOCKET s = ConnectUnixSocket(udsPath, - /*totalTimeoutMs*/ 5000, - /*retryDelayMs*/ 50); - - if (s == INVALID_SOCKET) + // Ignore cppcheck warning; if construction failed, an exception is expected first. + // cppcheck-suppress knownConditionTrueFalse + if (!connection) { return nullptr; } - SDV_LOG_INFO("[AF_UNIX] Access -> CREATE CLIENT for ", udsPath); + connection->SetWatchDogRemoveCallback([this](const void* instance) + { + m_watchdog.RemoveConnection(instance); + }); - // Client-side connection object (acceptRequired=false) - // Ownership is transferred to the caller (VAPI runtime) - return new CWinsockConnection(s, /*acceptRequired*/ false); + m_watchdog.AddConnection(connection); + return static_cast(connection.get()); } #endif \ No newline at end of file diff --git a/sdv_services/uds_win_sockets/channel_mgnt.h b/sdv_services/uds_win_sockets/channel_mgnt.h index 81ab015..dabdb9f 100644 --- a/sdv_services/uds_win_sockets/channel_mgnt.h +++ b/sdv_services/uds_win_sockets/channel_mgnt.h @@ -18,16 +18,14 @@ #include #include #include "connection.h" +#include "watchdog.h" + +#include -#include -#include -#include -#include #include #ifdef _WIN32 // Winsock headers are required for SOCKET / AF_UNIX / WSAStartup -// NOTE: The actual initialization is done via StartUpWinSock() # include #endif @@ -77,40 +75,6 @@ struct CAddrInfo addrinfo* AddressInfo { nullptr }; }; -/** - * @brief Initialize WinSock on Windows (idempotent) - * - * This helper ensures WSAStartup() is called only once in the process - * - * @return 0 on success, otherwise a WinSock error code - */ -inline int StartUpWinSock() -{ -#ifdef _WIN32 - static bool isInitialized = false; - if (isInitialized) - { - return 0; - } - - WSADATA wsaData {}; - const int error = WSAStartup(MAKEWORD(2, 2), &wsaData); - if (error != 0) - { - SDV_LOG_ERROR("WSAStartup failed with error: ", std::to_string(error)); - } - else - { - SDV_LOG_INFO("WSAStartup initialized"); - isInitialized = true; - } - return error; -#else - // Non-Windows: nothing to do. Return success for symmetry - return 0; -#endif -} - /** * @brief Simple pair of sockets used to connect two child processes * @@ -147,8 +111,8 @@ public: // Object declarations DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::system_object) DECLARE_OBJECT_CLASS_NAME("WinSocketsChannelControl") - DECLARE_OBJECT_CLASS_ALIAS("LocalChannelControl") - DECLARE_DEFAULT_OBJECT_NAME("LocalChannelControl") + DECLARE_OBJECT_CLASS_ALIAS("unix_domain_sockets") + DECLARE_DEFAULT_OBJECT_NAME("unix_domain_sockets") DECLARE_OBJECT_SINGLETON() virtual ~CSocketsChannelMgnt() = default; @@ -164,6 +128,11 @@ public: */ virtual void OnShutdown() override; + /** + * @brief Last function called before destruction. Overload of sdv::CSdvObject::OnDestroy. + */ + virtual void OnDestroy() override; + /** * @brief Create an IPC endpoint and return its connection information * @@ -208,29 +177,8 @@ public: */ sdv::IInterfaceAccess* Access(const sdv::u8string& ssConnectString) override; - /** - * @brief Called by a CWinsockConnection instance when the server side is closed - * - * Used to clean up internal registries for a given UDS path - * - * @param ptr Pointer to the CWinsockConnection instance that was closed - */ - void OnServerClosed(const std::string& udsPath, CWinsockConnection* ptr); - private: - /// @brief Registry of AF_UNIX server connections keyed by normalized UDS path - std::map> m_udsServers; - - /** - * @brief Set of UDS paths that already returned their server-side - * connection once via Access() - * - * This prevents returning the same server object multiple times - */ - std::unordered_set m_udsServerClaimed; - - /// @brief Mutex protecting m_udsServers and m_udsServerClaimed - std::mutex m_udsMtx; + CWinSocketsConnectionWatchDog m_watchdog; }; // SDV object factory macro diff --git a/sdv_services/uds_win_sockets/connection.cpp b/sdv_services/uds_win_sockets/connection.cpp index 1e73b92..7f36a89 100644 --- a/sdv_services/uds_win_sockets/connection.cpp +++ b/sdv_services/uds_win_sockets/connection.cpp @@ -17,11 +17,92 @@ #include #include #include +#include + +namespace +{ +static std::string ExpandEnvVarsLocal(const std::string& in) +{ + if (in.find('%') == std::string::npos) + { + return in; + } + + char buf[4096] = {}; + DWORD n = ExpandEnvironmentStringsA(in.c_str(), buf, static_cast(sizeof(buf))); + if (n > 0 && n < sizeof(buf)) + { + return std::string(buf); + } + + return in; +} + +static std::string ClampUdsPathLocal(const std::string& path) +{ + sockaddr_un tmp{}; + constexpr auto kMax = sizeof(tmp.sun_path) - 1; + return path.size() <= kMax ? path : path.substr(0, kMax); +} + +static std::string MakeShortWinUdsPathLocal(const std::string& raw) +{ + std::string path = ExpandEnvVarsLocal(raw); + const size_t pos = path.find_last_of("/\\"); + std::string base = (pos == std::string::npos) ? path : path.substr(pos + 1); + + if (base.empty()) + { + base = "sdv.sock"; + } + + std::string dir = ExpandEnvVarsLocal("%TEMP%\\sdv\\"); + CreateDirectoryA(dir.c_str(), nullptr); + return ClampUdsPathLocal(dir + base); +} + +static SOCKET ConnectUnixSocketLocal(const std::string& rawPath, uint32_t totalTimeoutMs, uint32_t retryDelayMs) +{ + const std::string udsPath = MakeShortWinUdsPathLocal(rawPath); + + sockaddr_un addr{}; + addr.sun_family = AF_UNIX; + strcpy_s(addr.sun_path, sizeof(addr.sun_path), udsPath.c_str()); + + const int addrlen = static_cast(offsetof(sockaddr_un, sun_path) + std::strlen(addr.sun_path) + 1); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(totalTimeoutMs); + + while (true) + { + SOCKET s = socket(AF_UNIX, SOCK_STREAM, 0); + if (s == INVALID_SOCKET) + { + return INVALID_SOCKET; + } + + if (connect(s, reinterpret_cast(&addr), addrlen) == 0) + { + return s; + } + + const int lastError = WSAGetLastError(); + closesocket(s); + + if (std::chrono::steady_clock::now() >= deadline) + { + SDV_LOG_ERROR("[AF_UNIX] lazy connect TIMEOUT, last WSA=", lastError, ", path='", udsPath, "'"); + return INVALID_SOCKET; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(retryDelayMs)); + } +} +} CWinsockConnection::CWinsockConnection(unsigned long long preconfiguredSocket, bool acceptConnectionRequired) : m_ConnectionState(sdv::ipc::EConnectState::uninitialized) , m_AcceptConnectionRequired(acceptConnectionRequired) - , m_CancelWait(false) + , m_UdsPath() { // Initialize legacy buffers with zero (kept for potential compatibility) std::fill(std::begin(m_SendBuffer), std::end(m_SendBuffer), '\0'); @@ -41,6 +122,17 @@ CWinsockConnection::CWinsockConnection(unsigned long long preconfiguredSocket, b } } +CWinsockConnection::CWinsockConnection(const std::string& udsPath) + : m_ConnectionState(sdv::ipc::EConnectState::uninitialized) + , m_AcceptConnectionRequired(false) + , m_UdsPath(udsPath) +{ + std::fill(std::begin(m_SendBuffer), std::end(m_SendBuffer), '\0'); + std::fill(std::begin(m_ReceiveBuffer), std::end(m_ReceiveBuffer), '\0'); + m_ListenSocket = INVALID_SOCKET; + m_ConnectionSocket = INVALID_SOCKET; +} + /*CWinsockConnection::CWinsockConnection() : m_ConnectionState(sdv::ipc::EConnectState::uninitialized) , m_AcceptConnectionRequired(false) @@ -65,28 +157,47 @@ CWinsockConnection::~CWinsockConnection() } } +void CWinsockConnection::SetWatchDogRemoveCallback(std::function callback) +{ + std::lock_guard lock(m_WatchdogMtx); + m_WatchdogRemoveCallback = std::move(callback); +} + void CWinsockConnection::SetConnectState(sdv::ipc::EConnectState state) { + sdv::ipc::IConnectEventCallback* pEventLocal = nullptr; + { std::lock_guard lk(m_MtxConnect); m_ConnectionState.store(state, std::memory_order_release); + + // Main receiver has priority over registered state listener + if (m_pMainEvent) + { + pEventLocal = m_pMainEvent; + } + else + { + pEventLocal = m_pRegisteredEvent; + } } // Wake up any waiter m_CvConnect.notify_all(); // Notify event callback if registered - if (m_pEvent) + if (pEventLocal) { try { - m_pEvent->SetConnectState(state); + pEventLocal->SetConnectState(state); } catch (...) { // Ignore callbacks throwing exceptions } } + } int32_t CWinsockConnection::Send(const char* data, int32_t dataLength) @@ -177,7 +288,7 @@ bool CWinsockConnection::SendData(/*inout*/ sdv::sequence> (kMaxUdsPacketSize - static_cast(sizeof(SFragmentedMsgHdr))) : 0; - if (maxPayloadFrag == 0) + if constexpr (maxPayloadFrag == 0) { return false; } @@ -318,9 +429,18 @@ SOCKET CWinsockConnection::AcceptConnection() tv.tv_usec = 50 * 1000; // 50 ms const int sr = ::select(0, &rfds, nullptr, nullptr, &tv); + if (sr == SOCKET_ERROR) { - SDV_LOG_ERROR("[AF_UNIX] select(listen) FAIL, WSA=", WSAGetLastError()); + const int err = WSAGetLastError(); + + if (m_StopConnectThread.load() || m_ListenSocket == INVALID_SOCKET) + { + SDV_LOG_INFO("[AF_UNIX] select(listen) canceled during shutdown, WSA=", err); + return INVALID_SOCKET; + } + + SDV_LOG_ERROR("[AF_UNIX] select(listen) FAIL, WSA=", err); SetConnectState(sdv::ipc::EConnectState::connection_error); return INVALID_SOCKET; } @@ -331,9 +451,17 @@ SOCKET CWinsockConnection::AcceptConnection() } SOCKET c = ::accept(m_ListenSocket, nullptr, nullptr); + if (c == INVALID_SOCKET) { const int err = WSAGetLastError(); + + if (m_StopConnectThread.load() || m_ListenSocket == INVALID_SOCKET || err == WSAENOTSOCK) + { + SDV_LOG_INFO("[AF_UNIX] accept canceled during shutdown, WSA=", err); + return INVALID_SOCKET; + } + if (err == WSAEINTR || err == WSAEWOULDBLOCK) { continue; @@ -355,23 +483,53 @@ SOCKET CWinsockConnection::AcceptConnection() bool CWinsockConnection::AsyncConnect(sdv::IInterfaceAccess* pReceiver) { + auto expectedState = m_ConnectionState.load(std::memory_order_acquire); + while (true) + { + if (expectedState == sdv::ipc::EConnectState::connected) + { + return true; + } + + if (expectedState == sdv::ipc::EConnectState::initializing) + { + SDV_LOG_WARNING("[AF_UNIX] AsyncConnect ignored: connect worker already running"); + return false; + } + + if (m_ConnectionState.compare_exchange_weak( + expectedState, + sdv::ipc::EConnectState::initializing, + std::memory_order_acq_rel, + std::memory_order_acquire)) + { + break; + } + } + // Store callbacks - m_pReceiver = sdv::TInterfaceAccessPtr(pReceiver).GetInterface(); - m_pEvent = sdv::TInterfaceAccessPtr(pReceiver).GetInterface(); + auto* pReceiverIfc = sdv::TInterfaceAccessPtr(pReceiver).GetInterface(); + auto* pEventIfc = sdv::TInterfaceAccessPtr(pReceiver).GetInterface(); + { + std::lock_guard lk(m_MtxConnect); + m_pReceiver = pReceiverIfc; + m_pMainEvent = pEventIfc; + } // Reset stop flags m_StopReceiveThread.store(false); m_StopConnectThread.store(false); m_CancelWait.store(false); - // Join old threads if any + // Join old workers only after they completed. + // Joining an active worker here can block forever when a previous connect is still pending. if (m_ReceiveThread.joinable()) m_ReceiveThread.join(); if (m_ConnectThread.joinable()) m_ConnectThread.join(); // Start the connect worker - m_ConnectThread = std::thread(&CWinsockConnection::ConnectWorker, this); + m_ConnectThread = sdv::core::secure_thread(&CWinsockConnection::ConnectWorker, this); return true; } @@ -428,11 +586,16 @@ void CWinsockConnection::Disconnect() uint64_t CWinsockConnection::RegisterStateEventCallback(/*in*/ sdv::IInterfaceAccess* pEventCallback) { + auto* pEventIfc = sdv::TInterfaceAccessPtr(pEventCallback).GetInterface(); + // Extract IConnectEventCallback interface - m_pEvent = sdv::TInterfaceAccessPtr(pEventCallback).GetInterface(); + { + std::lock_guard lk(m_MtxConnect); + m_pRegisteredEvent = pEventIfc; + } // Only one callback is supported; cookie 1 = valid - return (m_pEvent != nullptr) ? 1ULL : 0ULL; + return (pEventIfc != nullptr) ? 1ULL : 0ULL; } void CWinsockConnection::UnregisterStateEventCallback(/*in*/ uint64_t uiCookie) @@ -440,7 +603,9 @@ void CWinsockConnection::UnregisterStateEventCallback(/*in*/ uint64_t uiCookie) // Only one callback supported -> cookie value is 1 if (uiCookie == 1ULL) { - m_pEvent = nullptr; + std::lock_guard lk(m_MtxConnect); + m_pMainEvent = nullptr; + m_pRegisteredEvent = nullptr; } } @@ -451,11 +616,38 @@ sdv::ipc::EConnectState CWinsockConnection::GetConnectState() const void CWinsockConnection::DestroyObject() { - m_StopReceiveThread = true; - m_StopConnectThread = true; - StopThreadsAndCloseSockets(); - m_ConnectionState = sdv::ipc::EConnectState::disconnected; + bool expected = false; + if (!m_DestroyObjectCalled.compare_exchange_strong(expected, true)) + { + return; + } + + m_StopReceiveThread.store(true); + m_StopConnectThread.store(true); + + // Optional: notify terminating before releasing callbacks + SetConnectState(sdv::ipc::EConnectState::terminating); + + Disconnect(); + + // Release callbacks only during final teardown + { + std::lock_guard lk(m_MtxConnect); + m_pReceiver = nullptr; + m_pMainEvent = nullptr; + m_pRegisteredEvent = nullptr; + } + + std::function removeCallback; + { + std::lock_guard lock(m_WatchdogMtx); + removeCallback = std::move(m_WatchdogRemoveCallback); + } + if (removeCallback) + { + removeCallback(this); + } } void CWinsockConnection::ConnectWorker() @@ -482,16 +674,6 @@ void CWinsockConnection::ConnectWorker() if (c == INVALID_SOCKET) { - if (m_pEvent) - { - try - { - m_pEvent->SetConnectState(m_ConnectionState); - } - catch (...) - { - } - } return; } @@ -505,8 +687,22 @@ void CWinsockConnection::ConnectWorker() // CLIENT SIDE if (m_ConnectionSocket == INVALID_SOCKET) { - SetConnectState(sdv::ipc::EConnectState::connection_error); - return; + SetConnectState(sdv::ipc::EConnectState::initializing); + + if (m_UdsPath.empty()) + { + SetConnectState(sdv::ipc::EConnectState::connection_error); + return; + } + + SOCKET s = ConnectUnixSocketLocal(m_UdsPath, 5000, 50); + if (s == INVALID_SOCKET) + { + SetConnectState(sdv::ipc::EConnectState::connection_error); + return; + } + + m_ConnectionSocket = s; } } @@ -528,7 +724,7 @@ void CWinsockConnection::StartReceiveThread_Unsafe() } m_StopReceiveThread.store(false); - m_ReceiveThread = std::thread(&CWinsockConnection::ReceiveMessages, this); + m_ReceiveThread = sdv::core::secure_thread(&CWinsockConnection::ReceiveMessages, this); } void CWinsockConnection::StopThreadsAndCloseSockets() @@ -583,6 +779,7 @@ void CWinsockConnection::StopThreadsAndCloseSockets() SDV_LOG_INFO("[AF_UNIX] StopThreadsAndCloseSockets: closing listen=%llu conn=%llu", static_cast(l), static_cast(s)); + } bool CWinsockConnection::ReadNumberOfBytes(char* buffer, uint32_t length) @@ -608,7 +805,7 @@ bool CWinsockConnection::ReadNumberOfBytes(char* buffer, uint32_t length) continue; } - SDV_LOG_WARNING("[UDS][RX] recv() error: ", std::strerror(err)); + SDV_LOG_WARNING("[UDS][RX] recv() FAIL, WSA=", err); return false; } @@ -742,8 +939,13 @@ bool CWinsockConnection::ReadDataChunk(const CMessage& message, uint32_t offset, #if ENABLE_DECOUPLING > 0 // optional queueing path... #else - if (m_pReceiver) - m_pReceiver->ReceiveData(dataCtx.seqDataChunks); + sdv::ipc::IDataReceiveCallback* pReceiverLocal = nullptr; + { + std::lock_guard lk(m_MtxConnect); + pReceiverLocal = m_pReceiver; + } + if (pReceiverLocal) + pReceiverLocal->ReceiveData(dataCtx.seqDataChunks); dataCtx = SDataContext(); // reset context #endif break; diff --git a/sdv_services/uds_win_sockets/connection.h b/sdv_services/uds_win_sockets/connection.h index 87a2064..9c29275 100644 --- a/sdv_services/uds_win_sockets/connection.h +++ b/sdv_services/uds_win_sockets/connection.h @@ -27,6 +27,8 @@ #include #include #include +#include +#include #ifdef _WIN32 # include @@ -70,11 +72,12 @@ struct SMsgHeader * - sdv::ipc::IConnect : async connect / wait / state / events * - sdv::IObjectDestroy : explicit destruction hook for SDV runtime */ -class CWinsockConnection - : public sdv::IInterfaceAccess - , public sdv::ipc::IDataSend - , public sdv::ipc::IConnect - , public sdv::IObjectDestroy +class CWinsockConnection : + //public sdv::CSharedLifetimeControlImpl, public sdv::ipc::IDataSend, public sdv::ipc::IConnect + public sdv::IInterfaceAccess, + public sdv::ipc::IDataSend, + public sdv::ipc::IConnect, + public sdv::IObjectDestroy { public: /** @@ -89,6 +92,12 @@ public: */ CWinsockConnection(unsigned long long preconfiguredSocket, bool acceptConnectionRequired); + /** + * @brief Create a client endpoint that connects lazily in AsyncConnect. + * @param[in] udsPath Normalized path for AF_UNIX connection. + */ + explicit CWinsockConnection(const std::string& udsPath); + /** * @brief Virtual destructor needed for "delete this;" */ @@ -98,6 +107,7 @@ public: SDV_INTERFACE_ENTRY(sdv::ipc::IDataSend) SDV_INTERFACE_ENTRY(sdv::ipc::IConnect) SDV_INTERFACE_ENTRY(sdv::IObjectDestroy) + //SDV_INTERFACE_CHAIN_BASE(sdv::CSharedLifetimeControlImpl) END_SDV_INTERFACE_MAP() /** @@ -188,23 +198,28 @@ public: */ void DestroyObject() override; + /** @brief Register callback used to remove this connection from manager watchdog storage. */ + void SetWatchDogRemoveCallback(std::function callback); + private: std::mutex m_MtxConnect; std::condition_variable m_CvConnect; - std::thread m_ReceiveThread; ///< Thread which receives data from the socket - std::thread m_ConnectThread; + sdv::core::secure_thread m_ReceiveThread; ///< Thread which receives data from the socket + sdv::core::secure_thread m_ConnectThread; std::atomic m_StopReceiveThread{false}; ///< bool variable to stop thread std::atomic m_StopConnectThread{false}; std::atomic m_ConnectionState; ///< the state of the connection - sdv::ipc::IDataReceiveCallback* m_pReceiver = nullptr; ///< Receiver to pass the messages if available - sdv::ipc::IConnectEventCallback* m_pEvent = nullptr; ///< Event receiver + sdv::ipc::IDataReceiveCallback* m_pReceiver{nullptr}; ///< Receiver to pass the messages if available + sdv::ipc::IConnectEventCallback* m_pMainEvent{nullptr}; ///< Event receiver + sdv::ipc::IConnectEventCallback* m_pRegisteredEvent{nullptr}; bool m_AcceptConnectionRequired; ///< if true connection has to be accepted before receive thread can be started mutable std::recursive_mutex m_SendMutex; ///< Synchronize all packages to be send SOCKET m_ListenSocket{INVALID_SOCKET}; ///< Server-side listening socket SOCKET m_ConnectionSocket{INVALID_SOCKET}; ///< Active connected socket (client <-> server) + std::string m_UdsPath; ///< Client-side AF_UNIX path (lazy connect) static constexpr uint32_t m_SendMessageSize{ 1024 }; ///< size for the message to be send static constexpr uint32_t m_SendBufferSize = sizeof(SMsgHeader) + m_SendMessageSize; ///< Initial size of the send buffer @@ -213,6 +228,9 @@ private: uint32_t m_ReceiveBufferLength = sizeof(SMsgHeader); ///< receive buffer length std::atomic m_CancelWait{false}; + std::mutex m_WatchdogMtx; + std::function m_WatchdogRemoveCallback; + std::atomic m_DestroyObjectCalled{false}; /// @brief Server accept loop / client connect confirmation void ConnectWorker(); diff --git a/sdv_services/uds_win_sockets/watchdog.cpp b/sdv_services/uds_win_sockets/watchdog.cpp new file mode 100644 index 0000000..959082b --- /dev/null +++ b/sdv_services/uds_win_sockets/watchdog.cpp @@ -0,0 +1,58 @@ +/******************************************************************************** +* Copyright (c) 2025-2026 ZF Friedrichshafen AG +* +* This program and the accompanying materials are made available under the +* terms of the Apache License Version 2.0 which is available at +* https://www.apache.org/licenses/LICENSE-2.0 +* +* SPDX-License-Identifier: Apache-2.0 +* +* Contributors: +* Denisa Ros - initial API and implementation +********************************************************************************/ + +#ifdef _WIN32 + +#include "watchdog.h" + +void CWinSocketsConnectionWatchDog::AddConnectionImpl(const std::shared_ptr& connection) +{ + std::lock_guard lock(m_Mutex); + m_Connections[connection.get()] = connection; +} + +void CWinSocketsConnectionWatchDog::RemoveConnection(const void* connection) +{ + if (!connection) + { + return; + } + + std::shared_ptr removed; + { + std::lock_guard lock(m_Mutex); + auto it = m_Connections.find(connection); + if (it == m_Connections.end()) + { + return; + } + + removed = std::move(it->second); + m_Connections.erase(it); + } + + removed.reset(); +} + +void CWinSocketsConnectionWatchDog::Clear() +{ + std::map> localConnections; + { + std::lock_guard lock(m_Mutex); + localConnections.swap(m_Connections); + } + + localConnections.clear(); +} + +#endif // _WIN32 diff --git a/sdv_services/uds_win_sockets/watchdog.h b/sdv_services/uds_win_sockets/watchdog.h new file mode 100644 index 0000000..4bed92a --- /dev/null +++ b/sdv_services/uds_win_sockets/watchdog.h @@ -0,0 +1,47 @@ +/******************************************************************************** +* Copyright (c) 2025-2026 ZF Friedrichshafen AG +* +* This program and the accompanying materials are made available under the +* terms of the Apache License Version 2.0 which is available at +* https://www.apache.org/licenses/LICENSE-2.0 +* +* SPDX-License-Identifier: Apache-2.0 +* +* Contributors: +* Denisa Ros - initial API and implementation +********************************************************************************/ + +#ifdef _WIN32 +#ifndef UDS_WIN_SOCKETS_WATCHDOG_H +#define UDS_WIN_SOCKETS_WATCHDOG_H + +#include +#include +#include + +class CWinSocketsConnectionWatchDog +{ +public: + template + void AddConnection(const std::shared_ptr& connection) + { + if (!connection) + { + return; + } + + AddConnectionImpl(std::static_pointer_cast(connection)); + } + + void RemoveConnection(const void* connection); + void Clear(); + +private: + void AddConnectionImpl(const std::shared_ptr& connection); + + std::mutex m_Mutex; + std::map> m_Connections; +}; + +#endif // UDS_WIN_SOCKETS_WATCHDOG_H +#endif // _WIN32 diff --git a/sdv_services/uds_win_tunnel/CMakeLists.txt b/sdv_services/uds_win_tunnel/CMakeLists.txt index 3493a2a..bf41b3c 100644 --- a/sdv_services/uds_win_tunnel/CMakeLists.txt +++ b/sdv_services/uds_win_tunnel/CMakeLists.txt @@ -8,33 +8,64 @@ # SPDX-License-Identifier: Apache-2.0 #******************************************************************************* +if(WIN32) + # Define project project(uds_win_tunnel VERSION 1.0 LANGUAGES CXX) -# Define target -add_library(uds_win_tunnel STATIC +set(UDS_WIN_TUNNEL_SOURCES channel_mgnt.cpp connection.cpp + watchdog.cpp +) + +# Define targets +add_library(uds_win_tunnel STATIC + ${UDS_WIN_TUNNEL_SOURCES} +) + +add_library(uds_win_tunnel_module SHARED + ${UDS_WIN_TUNNEL_SOURCES} ) target_link_libraries(uds_win_tunnel PRIVATE uds_win_sockets - Ws2_32.lib + Ws2_32.lib ${CMAKE_THREAD_LIBS_INIT} ) -target_include_directories(uds_win_tunnel - PRIVATE +target_link_libraries(uds_win_tunnel_module + PRIVATE + uds_win_sockets + Ws2_32.lib + ${CMAKE_THREAD_LIBS_INIT} +) + +target_include_directories(uds_win_tunnel + PRIVATE ./include/ ../uds_win_sockets/ ) -set_target_properties(uds_win_tunnel PROPERTIES PREFIX "") -set_target_properties(uds_win_tunnel PROPERTIES SUFFIX ".sdv") +target_include_directories(uds_win_tunnel_module + PRIVATE + ./include/ + ../uds_win_sockets/ +) + +set_target_properties(uds_win_tunnel_module PROPERTIES + PREFIX "" + SUFFIX ".sdv" + OUTPUT_NAME "uds_win_tunnel" + ARCHIVE_OUTPUT_NAME "uds_win_tunnel_module" +) # Build dependencies add_dependencies(uds_win_tunnel CompileCoreIDL) +add_dependencies(uds_win_tunnel_module CompileCoreIDL) # Appending the service in the service list -set(SDV_Service_List ${SDV_Service_List} uds_win_tunnel PARENT_SCOPE) +set(SDV_Service_List ${SDV_Service_List} uds_win_tunnel_module PARENT_SCOPE) + +endif() diff --git a/sdv_services/uds_win_tunnel/channel_mgnt.cpp b/sdv_services/uds_win_tunnel/channel_mgnt.cpp index bfd29e4..51384c1 100644 --- a/sdv_services/uds_win_tunnel/channel_mgnt.cpp +++ b/sdv_services/uds_win_tunnel/channel_mgnt.cpp @@ -13,12 +13,14 @@ #ifdef _WIN32 #include "channel_mgnt.h" +#include +#include +#include +#include #include "../../global/base64.h" #include #include -#include - #pragma push_macro("interface") #undef interface @@ -39,298 +41,498 @@ #pragma pop_macro("GetObject") #pragma pop_macro("interface") -extern int StartUpWinSock(); +//#include "../sdv_services/uds_win_sockets/channel_mgnt.cpp" namespace { -/** - * @brief Parse a tunnel connect/config string and extract the path. - * - * Expected format: - * "proto=tunnel;path=;" - * - * Behavior: - * - If "proto=tunnel" missing -> false - * - If "path=" missing -> true and outPath.clear() - * - * @param[in] cs The connect/config string to parse. - * @param[out] outPath The extracted path, or empty if not found. - * @return true if parsing succeeded, false otherwise. - */ -static bool ParseTunnelPath(const std::string& cs, std::string& outPath) -{ - constexpr const char* protoKey = "proto=tunnel"; - constexpr const char* pathKey = "path="; - if (cs.find(protoKey) == std::string::npos) + static std::atomic g_nextChannelId{1}; + + static bool EnsureWSAInitialized() { - return false; + static std::once_flag s_once; + static bool s_ok = false; + + std::call_once(s_once, []() + { + WSADATA wsa{}; + const int rc = WSAStartup(MAKEWORD(2, 2), &wsa); + s_ok = (rc == 0); + if (!s_ok) + { + SDV_LOG_ERROR("[AF_UNIX] WSAStartup failed, rc=", rc); + } + }); + + return s_ok; } - const auto p = cs.find(pathKey); - if (p == std::string::npos) + static std::string SanitizeUdsName(std::string name) { - outPath.clear(); + for (char& ch : name) + { + const bool isAlphaNum = (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9'); + if (!isAlphaNum && ch != '_' && ch != '-') + { + ch = '_'; + } + } + + if (name.empty()) + { + name = "sdv"; + } + + return name; + } + + static std::string BuildNamedUdsRawPath(const std::string& channelName) + { + return "%LOCALAPPDATA%/sdv/" + SanitizeUdsName(channelName) + ".sock"; + } + + static std::string GetDefaultUdsRawPath() + { + uint32_t instanceId = 1000u; + const sdv::app::IAppContext* pAppContext = sdv::core::GetCore(); + if (pAppContext && pAppContext->GetInstanceID() != 0u) + { + instanceId = pAppContext->GetInstanceID(); + } + + return "%LOCALAPPDATA%/sdv/vapi_" + std::to_string(instanceId) + ".sock"; + } + + static std::string GetUniqueEndpointUdsRawPath() + { + static std::atomic nextEndpointId { 0u }; + + uint32_t instanceId = 1000u; + const sdv::app::IAppContext* pAppContext = sdv::core::GetCore(); + if (pAppContext && pAppContext->GetInstanceID() != 0u) + { + instanceId = pAppContext->GetInstanceID(); + } + + const uint32_t endpointId = nextEndpointId.fetch_add(1u, std::memory_order_relaxed); + return "%LOCALAPPDATA%/sdv/vapi_" + std::to_string(instanceId) + "_" + + std::to_string(static_cast(GetCurrentProcessId())) + "_" + std::to_string(endpointId) + ".sock"; + } + + /** + * @brief Parse a tunnel connect/config string and extract the path. + * + * Expected format: + * "proto=tunnel;path=;" + * + * Behavior: + * - If "proto=tunnel" missing -> false + * - If "path=" missing -> true and outPath.clear() + * + * @param[in] cs The connect/config string to parse. + * @param[out] outPath The extracted path, or empty if not found. + * @return true if parsing succeeded, false otherwise. + */ + static bool ParseTunnelPath(const std::string& cs, std::string& outPath) + { + constexpr const char* protoKey = "proto=tunnel"; + constexpr const char* pathKey = "path="; + + // Strict raw connect string only + if (cs.rfind(protoKey, 0) != 0) + { + return false; + } + + const auto p = cs.find(pathKey); + if (p == std::string::npos) + { + outPath.clear(); + return true; + } + + const auto start = p + std::strlen(pathKey); + const auto end = cs.find(';', start); + if (end == std::string::npos) + { + outPath = cs.substr(start); + } + else + { + outPath = cs.substr(start, end - start); + } return true; } - const auto start = p + std::strlen(pathKey); - const auto end = cs.find(';', start); - if (end == std::string::npos) + static std::string ExtractTunnelNameFromToml(sdv::toml::CTOMLParser& parser) { - outPath = cs.substr(start); - } - else - { - outPath = cs.substr(start, end - start); - } - return true; -} + std::string tunnel; -/** - * @brief Expands Windows environment variables in a string (e.g., %TEMP%). - * @param[in] in Input string possibly containing environment variables. - * @return String with environment variables expanded, or original if expansion fails. - */ -static std::string ExpandEnvVars(const std::string& in) -{ - if (in.find('%') == std::string::npos) + auto ipcTunnelNode = parser.GetDirect("IpcChannel.Tunnel"); + if (ipcTunnelNode.GetType() == sdv::toml::ENodeType::node_string) + { + tunnel = static_cast(ipcTunnelNode.GetValue()); + } + + if (tunnel.empty()) + { + auto providerTunnelNode = parser.GetDirect("Provider.Tunnel"); + if (providerTunnelNode.GetType() == sdv::toml::ENodeType::node_string) + { + tunnel = static_cast(providerTunnelNode.GetValue()); + } + } + + return tunnel; + } + + static bool ExtractTunnelConnectString(const std::string& in, + std::string& outTunnelConnectString) { + std::string path; + + // Case 1: strict raw tunnel connect string + if (ParseTunnelPath(in, path)) + { + outTunnelConnectString = in; + return true; + } + + // Case 2: structured TOML + sdv::toml::CTOMLParser parser(in); + if (!parser.IsValid()) + { + return false; + } + + const std::string providerName = parser.GetDirect("Provider.Name").GetValue(); + if (!providerName.empty() && + providerName != "unix_domain_sockets_tunnel" && + providerName != "WinTunnelChannelControl" && + providerName != "UnixTunnelChannelControl") + { + return false; + } + + // Provider.ConnectString = "proto=tunnel;path=...;tunnel=...;" + const std::string nested = parser.GetDirect("Provider.ConnectString").GetValue(); + if (!nested.empty()) + { + if (ParseTunnelPath(nested, path)) + { + outTunnelConnectString = nested; + return true; + } + return false; + } + + // Build a tunnel connect string from [IpcChannel] + const std::string tunnel = ExtractTunnelNameFromToml(parser); + + const std::string cfgPath = parser.GetDirect("IpcChannel.Path").GetValue(); + if (!cfgPath.empty()) + { + outTunnelConnectString = "proto=tunnel;path=" + cfgPath + ";"; + if (!tunnel.empty()) + { + outTunnelConnectString += "tunnel=" + tunnel + ";"; + } + return true; + } + + const std::string cfgName = parser.GetDirect("IpcChannel.Name").GetValue(); + if (!cfgName.empty()) + { + outTunnelConnectString = "proto=tunnel;path=" + BuildNamedUdsRawPath(cfgName) + ";"; + if (!tunnel.empty()) + { + outTunnelConnectString += "tunnel=" + tunnel + ";"; + } + return true; + } + + return false; + } + + /** + * @brief Expands Windows environment variables in a string (e.g., %TEMP%). + * @param[in] in Input string possibly containing environment variables. + * @return String with environment variables expanded, or original if expansion fails. + */ + static std::string ExpandEnvVars(const std::string& in) + { + if (in.find('%') == std::string::npos) + { + return in; + } + char buf[4096] = {}; + DWORD n = ExpandEnvironmentStringsA(in.c_str(), buf, static_cast(sizeof(buf))); + if (n > 0 && n < sizeof(buf)) + { + return std::string(buf); + } return in; } - char buf[4096] = {}; - DWORD n = ExpandEnvironmentStringsA(in.c_str(), buf, static_cast(sizeof(buf))); - if (n > 0 && n < sizeof(buf)) - { - return std::string(buf); - } - return in; -} -/** - * @brief Clamps a UDS path to the maximum allowed by SOCKADDR_UN. - * @param[in] p The input path. - * @return The clamped path. - */ -static std::string ClampUdsPath(const std::string& p) -{ - SOCKADDR_UN tmp{}; - constexpr auto kMax = sizeof(tmp.sun_path) - 1; - if (p.size() <= kMax) + /** + * @brief Clamps a UDS path to the maximum allowed by SOCKADDR_UN. + * @param[in] p The input path. + * @return The clamped path. + */ + static std::string ClampUdsPath(const std::string& p) { - return p; - } - return p.substr(0, kMax); -} - -// Only for logging – basename -/** - * @brief Normalizes a raw UDS path for Windows, extracting the basename and ensuring a default if empty. - * @param[in] raw The raw path string. - * @return The normalized basename, clamped to max length. - */ -static std::string NormalizeUdsPathForWindows(const std::string& raw) -{ - std::string p = ExpandEnvVars(raw); - const size_t pos = p.find_last_of("/\\"); - std::string base = (pos == std::string::npos) ? p : p.substr(pos + 1); - if (base.empty()) - { - base = "sdv_tunnel.sock"; - } - SDV_LOG_INFO("[AF_UNIX][Tunnel] Normalize raw='", raw, "' -> base='", base, "'"); - return ClampUdsPath(base); -} - -/** - * @brief Creates a short, safe UDS path in the Windows temp directory. - * @param[in] raw The raw path string. - * @return The full path in %TEMP%\sdv\, clamped to max length. - */ -static std::string MakeShortWinUdsPath(const std::string& raw) -{ - std::string p = ExpandEnvVars(raw); - const size_t pos = p.find_last_of("/\\"); - std::string base = (pos == std::string::npos) ? p : p.substr(pos + 1); - if (base.empty()) - { - base = "sdv_tunnel.sock"; + SOCKADDR_UN tmp{}; + constexpr auto kMax = sizeof(tmp.sun_path) - 1; + if (p.size() <= kMax) + { + return p; + } + return p.substr(0, kMax); } - std::string dir = ExpandEnvVars("%TEMP%\\sdv\\"); - CreateDirectoryA(dir.c_str(), nullptr); - const std::string full = dir + base; - - return ClampUdsPath(full); -} - -/** - * @brief Creates an AF_UNIX listen socket at the specified path. - * @param[in] rawPath The raw path for the socket. - * @return The created socket handle, or INVALID_SOCKET on failure. - */ -static SOCKET CreateUnixListenSocket(const std::string& rawPath) -{ - SOCKET s = socket(AF_UNIX, SOCK_STREAM, 0); - if (s == INVALID_SOCKET) + static std::string BuildFinalUdsPath(const std::string& rawPath) { - SDV_LOG_ERROR("[AF_UNIX][Tunnel] socket() FAIL (listen), WSA=", WSAGetLastError()); - return INVALID_SOCKET; + // Expand environment variables + std::string full = ExpandEnvVars(rawPath); + + // Ensure directory exists (parent folder) + auto pos = full.find_last_of("\\/"); + if (pos != std::string::npos) + { + std::string dir = full.substr(0, pos); + + // Ensure immediate parent exists (base/tunnel directory is created earlier) + CreateDirectoryA(dir.c_str(), nullptr); + } + + // Clamp to AF_UNIX limit + return ClampUdsPath(full); + } + + /** + * @brief Normalizes a raw UDS path for Windows, extracting the basename and ensuring a default if empty. + * @param[in] raw The raw path string. + * @return The normalized basename, clamped to max length. + */ + static std::string NormalizeUdsPathForWindows(const std::string& raw) + { + std::string p = ExpandEnvVars(raw); + const size_t pos = p.find_last_of("/\\"); + std::string base = (pos == std::string::npos) ? p : p.substr(pos + 1); + if (base.empty()) + { + base = "sdv_tunnel.sock"; + } + SDV_LOG_INFO("[AF_UNIX][Tunnel] Normalize raw='", raw, "' -> base='", base, "'"); + return ClampUdsPath(base); } - std::string udsPath = MakeShortWinUdsPath(rawPath); - SOCKADDR_UN addr{}; - addr.sun_family = AF_UNIX; - strcpy_s(addr.sun_path, sizeof(addr.sun_path), udsPath.c_str()); - - const int addrlen = static_cast( - offsetof(SOCKADDR_UN, sun_path) + std::strlen(addr.sun_path) + 1); - - ::remove(udsPath.c_str()); - if (bind(s, reinterpret_cast(&addr), addrlen) == SOCKET_ERROR) + /** + * @brief Creates an AF_UNIX listen socket at the specified path. + * @param[in] rawPath The raw path for the socket. + * @return The created socket handle, or INVALID_SOCKET on failure. + */ + static SOCKET CreateUnixListenSocket(const std::string& rawPath) { - SDV_LOG_ERROR("[AF_UNIX][Tunnel] bind FAIL, WSA=", - WSAGetLastError(), ", path='", udsPath, "'"); - closesocket(s); - return INVALID_SOCKET; - } - if (listen(s, SOMAXCONN) == SOCKET_ERROR) - { - SDV_LOG_ERROR("[AF_UNIX][Tunnel] listen FAIL, WSA=", - WSAGetLastError(), ", path='", udsPath, "'"); - closesocket(s); - return INVALID_SOCKET; - } + + if (!EnsureWSAInitialized()) + return INVALID_SOCKET; - SDV_LOG_INFO("[AF_UNIX][Tunnel] bind+listen OK, path='", udsPath, "'"); - return s; -} - -/** - * @brief Connects to an AF_UNIX socket at the specified path, retrying until timeout. - * @param[in] rawPath The raw path to connect to. - * @param[in] totalTimeoutMs Total timeout in milliseconds. - * @param[in] retryDelayMs Delay between retries in milliseconds. - * @return The connected socket handle, or INVALID_SOCKET on failure. - */ -static SOCKET ConnectUnixSocket( - const std::string& rawPath, - uint32_t totalTimeoutMs, - uint32_t retryDelayMs) -{ - const std::string udsPath = MakeShortWinUdsPath(rawPath); - SOCKADDR_UN addr{}; - addr.sun_family = AF_UNIX; - strcpy_s(addr.sun_path, sizeof(addr.sun_path), udsPath.c_str()); - - const int addrlen = static_cast( - offsetof(SOCKADDR_UN, sun_path) + std::strlen(addr.sun_path) + 1); - - const auto deadline = std::chrono::steady_clock::now() + - std::chrono::milliseconds(totalTimeoutMs); - - int lastError = 0; - while (true) - { SOCKET s = socket(AF_UNIX, SOCK_STREAM, 0); if (s == INVALID_SOCKET) { - lastError = WSAGetLastError(); - SDV_LOG_ERROR("[AF_UNIX][Tunnel] socket() FAIL (client), WSA=", lastError); + SDV_LOG_ERROR("[AF_UNIX] socket FAIL (listen), WSA=", WSAGetLastError()); return INVALID_SOCKET; } - if (connect(s, reinterpret_cast(&addr), addrlen) == 0) - { - SDV_LOG_INFO("[AF_UNIX][Tunnel] connect OK, path='", udsPath, "'"); - return s; - } + //bulletproof path handling + std::string udsPath = BuildFinalUdsPath(rawPath); - lastError = WSAGetLastError(); - closesocket(s); + SOCKADDR_UN addr{}; + addr.sun_family = AF_UNIX; + strcpy_s(addr.sun_path, sizeof(addr.sun_path), udsPath.c_str()); - if (std::chrono::steady_clock::now() >= deadline) + const int addrlen = static_cast( + offsetof(SOCKADDR_UN, sun_path) + std::strlen(addr.sun_path) + 1); + + ::remove(udsPath.c_str()); + + if (bind(s, reinterpret_cast(&addr), addrlen) == SOCKET_ERROR) { - SDV_LOG_ERROR("[AF_UNIX][Tunnel] connect TIMEOUT, last WSA=", - lastError, ", path='", udsPath, "'"); + int err = WSAGetLastError(); + SDV_LOG_ERROR("[AF_UNIX] bind FAIL, WSA=", err, ", path=", udsPath); + closesocket(s); return INVALID_SOCKET; } - std::this_thread::sleep_for(std::chrono::milliseconds(retryDelayMs)); + if (listen(s, SOMAXCONN) == SOCKET_ERROR) + { + int err = WSAGetLastError(); + SDV_LOG_ERROR("[AF_UNIX] listen FAIL, WSA=", err, ", path=", udsPath); + closesocket(s); + return INVALID_SOCKET; + } + + SDV_LOG_INFO("[AF_UNIX] bind+listen OK: ", udsPath); + return s; } -} + + static bool ExtractTunnelName(const std::string& in, std::string& outTunnel) + { + auto pos = in.find("tunnel="); + if (pos == std::string::npos) + { + outTunnel.clear(); + return false; + } + pos += 7; + auto end = in.find(';', pos); + outTunnel = (end == std::string::npos) ? in.substr(pos) : in.substr(pos, end - pos); + return !outTunnel.empty(); + } + + /** + * @brief Connects to an AF_UNIX socket at the specified path, retrying until timeout. + * @param[in] rawPath The raw path to connect to.CreateUnixListenSocket + * @param[in] totalTimeoutMs Total timeout in milliseconds. + * @param[in] retryDelayMs Delay between retries in milliseconds. + * @return The connected socket handle, or INVALID_SOCKET on failure. + */ + static SOCKET ConnectUnixSocket( + const std::string& rawPath, + uint32_t totalTimeoutMs, + uint32_t retryDelayMs) + { + if (!EnsureWSAInitialized()) + return INVALID_SOCKET; + + //SAME path logic ca server + const std::string udsPath = BuildFinalUdsPath(rawPath); + SDV_LOG_INFO("[AF_UNIX][Tunnel] Attempting to connect to ", udsPath, "with rawPath=", rawPath); + SOCKADDR_UN addr{}; + addr.sun_family = AF_UNIX; + strcpy_s(addr.sun_path, sizeof(addr.sun_path), udsPath.c_str()); + + const int addrlen = static_cast( + offsetof(SOCKADDR_UN, sun_path) + std::strlen(addr.sun_path) + 1); + + auto deadline = std::chrono::steady_clock::now() + + std::chrono::milliseconds(totalTimeoutMs); + + while (true) + { + SOCKET s = socket(AF_UNIX, SOCK_STREAM, 0); + if (s == INVALID_SOCKET) + { + SDV_LOG_ERROR("[AF_UNIX] socket FAIL (client), WSA=", WSAGetLastError()); + return INVALID_SOCKET; + } + + if (connect(s, reinterpret_cast(&addr), addrlen) == 0) + { + SDV_LOG_INFO("[AF_UNIX] connect OK: ", udsPath); + return s; + } + + int err = WSAGetLastError(); + closesocket(s); + + if (std::chrono::steady_clock::now() >= deadline) + { + SDV_LOG_ERROR("[AF_UNIX] connect TIMEOUT, WSA=", err, ", path=", udsPath); + return INVALID_SOCKET; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(retryDelayMs)); + } + } } // anonymous namespace bool CSocketsTunnelChannelMgnt::OnInitialize() { - return true; + return EnsureWSAInitialized(); } void CSocketsTunnelChannelMgnt::OnShutdown() {} -// -------- Server bookkeeping (optional) -------- -void CSocketsTunnelChannelMgnt::OnServerClosed(const std::string& udsPath, CWinTunnelConnection* ptr) +void CSocketsTunnelChannelMgnt::OnDestroy() { - std::lock_guard lock(m_udsMtx); - auto it = m_udsServers.find(udsPath); - if (it != m_udsServers.end() && it->second.get() == ptr) - { - m_udsServers.erase(it); - } - m_udsServerClaimed.erase(udsPath); + m_watchdog.Clear(); } -// -------- ICreateEndpoint -------- + +// Note: +// tunnel name is required by the current Windows tunnel design for namespace isolation. +// [IpcChannel.Path]/[IpcChannel.Name] alone are not sufficient unless Tunnel is also provided. sdv::ipc::SChannelEndpoint CSocketsTunnelChannelMgnt::CreateEndpoint(const sdv::u8string& cfgStr) { sdv::ipc::SChannelEndpoint ep{}; - - if (StartUpWinSock() != 0) - { - SDV_LOG_ERROR("[AF_UNIX][Tunnel] WinSock startup failed in CreateEndpoint"); - return ep; - } - - // Optional TOML config: [IpcChannel] Path = "..." std::string udsRaw; - if (!cfgStr.empty()) - { - //for toml file - bool isTOML = cfgStr.find('=') == std::string::npos; - if(isTOML) - { - sdv::toml::CTOMLParser cfg(cfgStr.c_str()); - auto pathNode = cfg.GetDirect("IpcChannel.Path"); - if (pathNode.GetType() == sdv::toml::ENodeType::node_string) - { - udsRaw = static_cast(pathNode.GetValue()); - } - } + std::string tunnelConnectString; - //for connect string - if (udsRaw.empty()) + if (ExtractTunnelConnectString(cfgStr, tunnelConnectString)) + { + ParseTunnelPath(tunnelConnectString, udsRaw); + } + else if (!cfgStr.empty()) + { + sdv::toml::CTOMLParser parser(cfgStr); + const std::string cfgPath = parser.GetDirect("IpcChannel.Path").GetValue(); + const std::string cfgName = parser.GetDirect("IpcChannel.Name").GetValue(); + + if (!cfgPath.empty()) { - const std::string s(cfgStr); - const std::string key = "path="; - auto pos = s.find(key); - if (pos != std::string::npos) - { - auto end = s.find(';', pos + key.size()); - if (end == std::string::npos) - udsRaw = s.substr(pos + key.size()); - else - udsRaw = s.substr(pos + key.size(), end - pos - key.size()); - } + udsRaw = cfgPath; + } + else if (!cfgName.empty()) + { + udsRaw = BuildNamedUdsRawPath(cfgName); + } + else + { + udsRaw = GetDefaultUdsRawPath(); } } if (udsRaw.empty()) { - udsRaw = "%LOCALAPPDATA%/sdv/tunnel.sock"; + udsRaw = cfgStr.empty() ? GetUniqueEndpointUdsRawPath() : GetDefaultUdsRawPath(); + } + + if (tunnelConnectString.empty()) + { + tunnelConnectString = cfgStr; + } + + std::string tunnel; + if (!ExtractTunnelName(tunnelConnectString, tunnel)) + { + // fallback for AppConnect (config minimal) + sdv::toml::CTOMLParser parser(cfgStr); + const std::string cfgName = parser.GetDirect("IpcChannel.Name").GetValue(); + + if (!cfgName.empty()) + { + tunnel = cfgName; + SDV_LOG_INFO("[AF_UNIX][Tunnel] Using channel name as tunnel name: ", tunnel); + } + else + { + // fallback + tunnel = "default"; + SDV_LOG_WARNING("[AF_UNIX][Tunnel] Missing tunnel and channel name, using default"); + } } - std::string udsPathBase = NormalizeUdsPathForWindows(udsRaw); + std::string base = ExpandEnvVars("%TEMP%\\sdv\\"); + CreateDirectoryA(base.c_str(), nullptr); + + std::string dir = base + tunnel + "\\"; + CreateDirectoryA(dir.c_str(), nullptr); + + std::string udsPathBase = dir + NormalizeUdsPathForWindows(udsRaw); + SDV_LOG_INFO("[AF_UNIX][Tunnel] endpoint udsPath=", udsPathBase); SOCKET listenSocket = CreateUnixListenSocket(udsPathBase); @@ -340,75 +542,177 @@ sdv::ipc::SChannelEndpoint CSocketsTunnelChannelMgnt::CreateEndpoint(const sdv:: return ep; } - auto serverTransport = std::make_shared( static_cast(listenSocket), true); - auto serverTunnel = std::make_shared(serverTransport, /*channelId*/ static_cast(0u)); - + auto serverTransport = std::make_shared(static_cast(listenSocket), true); + uint32_t chId = g_nextChannelId++; + auto serverTunnel = std::make_shared(serverTransport, /*channelId*/ static_cast(chId)); + // Ignore cppcheck warning; if construction failed, an exception is expected first. + // cppcheck-suppress knownConditionTrueFalse + if (!serverTunnel) { - std::lock_guard lock(m_udsMtx); - m_udsServers[udsPathBase] = serverTunnel; - m_udsServerClaimed.erase(udsPathBase); + return ep; + } + // Retain shared_ptr to keep object alive for the duration of the connection. + // This ensures shared_from_this() works correctly in DestroyObject(). + { + std::lock_guard lock(m_ConnectionsMutex); + m_ptrConnections[serverTunnel.get()] = serverTunnel; } + serverTunnel->SetWatchDogRemoveCallback([this](const void* connection) + { + m_watchdog.RemoveConnection(connection); + // Remove the retained shared_ptr when connection is destroyed + std::lock_guard lock(m_ConnectionsMutex); + m_ptrConnections.erase(connection); + }); + + m_watchdog.AddConnection(serverTunnel); + ep.pConnection = static_cast(serverTunnel.get()); - ep.ssConnectString = "proto=tunnel;role=server;path=" + udsPathBase + ";"; + + const std::string clientConnectString = "proto=tunnel;path=" + udsPathBase + ";tunnel=" + tunnel + ";"; + + // Publish raw connect string (cleaner and avoids TOML escaping issues on Windows) + ep.ssConnectString = clientConnectString; return ep; } sdv::IInterfaceAccess* CSocketsTunnelChannelMgnt::Access(const sdv::u8string& cs) { - if (StartUpWinSock() != 0) + std::string tunnelConnectString; + const std::string input = static_cast(cs); + + // Operational metadata, not part of the tunnel connect string itself + const bool isServer = (input.find("role=server") != std::string::npos); + + // Parse only the real connect/config part + std::string parseInput = input; + const auto rolePos = parseInput.find(";role="); + if (rolePos != std::string::npos) { - SDV_LOG_ERROR("[AF_UNIX][Tunnel] WinSock startup failed in Access()" ); + const auto roleEnd = parseInput.find(';', rolePos + 1); + if (roleEnd != std::string::npos) + { + parseInput.erase(rolePos, roleEnd - rolePos + 1); + } + else + { + parseInput.erase(rolePos); + } + } + + if (!ExtractTunnelConnectString(parseInput, tunnelConnectString)) + { + SDV_LOG_ERROR("[AF_UNIX][Tunnel] Invalid tunnel connect/config string"); return nullptr; } - std::string connectStr = static_cast(cs); std::string udsRaw; - if (!ParseTunnelPath(connectStr, udsRaw)) - { - SDV_LOG_ERROR("[AF_UNIX][Tunnel] Invalid tunnel connect string: ", connectStr); - return nullptr; - } + ParseTunnelPath(tunnelConnectString, udsRaw); + SDV_LOG_INFO("[AF_UNIX][Tunnel] Access requested with connect string: ", + tunnelConnectString, ", extracted path: ", udsRaw); if (udsRaw.empty()) { - udsRaw = "%LOCALAPPDATA%/sdv/tunnel.sock"; + udsRaw = GetDefaultUdsRawPath(); + SDV_LOG_INFO("[AF_UNIX][Tunnel] No path specified, using default: ", udsRaw); } - std::string udsPathBase = NormalizeUdsPathForWindows(udsRaw); - SDV_LOG_INFO("[AF_UNIX][Tunnel] Access udsPath=", udsPathBase); - - const bool isServer = - (connectStr.find("role=server") != std::string::npos); + std::string tunnel; + if (!ExtractTunnelName(tunnelConnectString, tunnel)) { - std::lock_guard lock(m_udsMtx); - auto it = m_udsServers.find(udsPathBase); - if (isServer && it != m_udsServers.end() && it->second != nullptr) + // fallback: derive from filename + std::string path; + ParseTunnelPath(tunnelConnectString, path); + + // extract filename + auto pos = path.find_last_of("/\\"); + std::string filename = (pos != std::string::npos) ? path.substr(pos + 1) : path; + + // remove ".sock" + auto dot = filename.rfind(".sock"); + if (dot != std::string::npos) { - if (!m_udsServerClaimed.count(udsPathBase)) - { - m_udsServerClaimed.insert(udsPathBase); - SDV_LOG_INFO("[AF_UNIX][Tunnel] Access -> RETURN SERVER for ", udsPathBase); - return it->second.get(); // Ownership: managed by m_udsServers (do not delete) - } + tunnel = filename.substr(0, dot); + } + else + { + tunnel = filename; + } + + if (!tunnel.empty()) + { + SDV_LOG_INFO("[AF_UNIX][Tunnel] Derived tunnel from filename: ", tunnel); } } - // CLIENT: create AF_UNIX client socket and wrap it in a tunnel - SOCKET s = ConnectUnixSocket(udsPathBase, /*totalTimeoutMs*/ 5000, /*retryDelayMs*/ 50); - if (s == INVALID_SOCKET) + if (tunnel.empty()) { - SDV_LOG_ERROR("[AF_UNIX][Tunnel] Failed to connect client socket for ", udsPathBase); + SDV_LOG_ERROR("[AF_UNIX][Tunnel] Missing required tunnel name"); return nullptr; } - SDV_LOG_INFO("[AF_UNIX][Tunnel] Access -> CREATE CLIENT for ", udsPathBase); - auto clientTransport = std::make_shared(s, /*acceptRequired*/ false); - // Ownership: The returned pointer must be managed and deleted by the SDV framework via IObjectDestroy - auto* tunnelClient = new CWinTunnelConnection(clientTransport, /*channelId*/ 0u); + std::string base = ExpandEnvVars("%TEMP%\\sdv\\"); + CreateDirectoryA(base.c_str(), nullptr); - return static_cast(tunnelClient); + std::string dir = base + tunnel + "\\"; + CreateDirectoryA(dir.c_str(), nullptr); + + std::string udsPathBase = dir + NormalizeUdsPathForWindows(udsRaw); + SDV_LOG_INFO("[AF_UNIX][Tunnel] Access udsPath=", udsPathBase); + + std::shared_ptr connection; + if (isServer) + { + SOCKET listenSocket = CreateUnixListenSocket(udsPathBase); + if (listenSocket == INVALID_SOCKET) + { + SDV_LOG_ERROR("[AF_UNIX][Tunnel] Failed to create server socket for ", udsPathBase); + return nullptr; + } + + auto serverTransport = std::make_shared( + static_cast(listenSocket), true); + uint32_t chId = g_nextChannelId++; + connection = std::make_shared( + serverTransport, static_cast(chId)); + } + else + { + SOCKET s = ConnectUnixSocket(udsPathBase, 5000, 50); + if (s == INVALID_SOCKET) + { + SDV_LOG_ERROR("[AF_UNIX][Tunnel] Failed to connect client socket for ", udsPathBase); + return nullptr; + } + + SDV_LOG_INFO("[AF_UNIX][Tunnel] Access -> CREATE CLIENT for ", udsPathBase); + auto clientTransport = std::make_shared(s, false); + uint32_t chId = g_nextChannelId++; + connection = std::make_shared( + clientTransport, static_cast(chId)); + } + + if (!connection) + { + return nullptr; + } + + { + std::lock_guard lock(m_ConnectionsMutex); + m_ptrConnections[connection.get()] = connection; + } + + connection->SetWatchDogRemoveCallback([this](const void* instance) + { + m_watchdog.RemoveConnection(instance); + std::lock_guard lock(m_ConnectionsMutex); + m_ptrConnections.erase(instance); + }); + + m_watchdog.AddConnection(connection); + return static_cast(connection.get()); } #endif \ No newline at end of file diff --git a/sdv_services/uds_win_tunnel/channel_mgnt.h b/sdv_services/uds_win_tunnel/channel_mgnt.h index 89c48e6..9c09a92 100644 --- a/sdv_services/uds_win_tunnel/channel_mgnt.h +++ b/sdv_services/uds_win_tunnel/channel_mgnt.h @@ -16,18 +16,15 @@ #include #include -#include "../sdv_services/uds_win_sockets/channel_mgnt.h" -#include "../sdv_services/uds_win_sockets/connection.h" #include "connection.h" +#include "watchdog.h" -#include +#include #include -#include -#include +#include #include // Winsock headers are required for SOCKET / AF_UNIX / WSAStartup -// NOTE: The actual initialization is done via StartUpWinSock() #include class CWinTunnelConnection; @@ -56,8 +53,8 @@ public: DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::system_object) DECLARE_OBJECT_CLASS_NAME("WinTunnelChannelControl") - DECLARE_OBJECT_CLASS_ALIAS("LocalChannelControl") - DECLARE_DEFAULT_OBJECT_NAME("LocalChannelControl") + DECLARE_OBJECT_CLASS_ALIAS("unix_domain_sockets_tunnel") + DECLARE_DEFAULT_OBJECT_NAME("unix_domain_sockets_tunnel") DECLARE_OBJECT_SINGLETON() virtual ~CSocketsTunnelChannelMgnt() = default; @@ -73,6 +70,11 @@ public: */ virtual void OnShutdown() override; + /** + * @brief Last function called before destruction. Overload of sdv::CSdvObject::OnDestroy. + */ + virtual void OnDestroy() override; + /** * @brief Creates a tunnel endpoint (server side) and returns endpoint info. * @param[in] cfgStr Optional config string (TOML or connect string). @@ -87,17 +89,10 @@ public: */ sdv::IInterfaceAccess* Access(const sdv::u8string& cs) override; - /** - * @brief Called by server tunnel when closing (bookkeeping). - * @param[in] udsPath The UDS path for the server. - * @param[in] ptr Pointer to the tunnel connection being closed. - */ - void OnServerClosed(const std::string& udsPath, CWinTunnelConnection* ptr); - private: - std::mutex m_udsMtx; - std::map> m_udsServers; - std::set m_udsServerClaimed; + CWinTunnelConnectionWatchDog m_watchdog; + std::map> m_ptrConnections; ///< Retain shared_ptr for all connections + mutable std::mutex m_ConnectionsMutex; ///< Protect concurrent access to m_ptrConnections }; DEFINE_SDV_OBJECT(CSocketsTunnelChannelMgnt) diff --git a/sdv_services/uds_win_tunnel/connection.cpp b/sdv_services/uds_win_tunnel/connection.cpp index 9aa27fa..57dac10 100644 --- a/sdv_services/uds_win_tunnel/connection.cpp +++ b/sdv_services/uds_win_tunnel/connection.cpp @@ -24,26 +24,35 @@ CWinTunnelConnection::CWinTunnelConnection( // No additional initialization required; acts as a thin wrapper. } -bool CWinTunnelConnection::SendData( - /*inout*/ sdv::sequence>& seqData) +CWinTunnelConnection::~CWinTunnelConnection() { + +} + +void CWinTunnelConnection::SetWatchDogRemoveCallback(std::function callback) +{ + std::lock_guard lock(m_WatchdogMtx); + m_WatchdogRemoveCallback = std::move(callback); +} + +bool CWinTunnelConnection::SendData( /*inout*/ sdv::sequence>& seqData) +{ + if (!m_Transport) { SDV_LOG_ERROR("[WinTunnel] SendData failed: transport is null"); return false; } - // Build tunnel header buffer sdv::pointer hdrBuf; hdrBuf.resize(sizeof(STunnelHeader)); STunnelHeader hdr{}; - hdr.uiChannelId = m_ChannelId; // Logical channel for this connection - hdr.uiFlags = 0; // Reserved for future use + hdr.uiChannelId = m_ChannelId; + hdr.uiFlags = 0; std::memcpy(hdrBuf.get(), &hdr, sizeof(STunnelHeader)); - // Compose new sequence: [header] + original payload chunks sdv::sequence> seqWithHdr; seqWithHdr.push_back(hdrBuf); for (auto& chunk : seqData) @@ -51,11 +60,13 @@ bool CWinTunnelConnection::SendData( seqWithHdr.push_back(chunk); } - bool result = m_Transport->SendData(seqWithHdr); - if (!result) { + const bool result = m_Transport->SendData(seqWithHdr); + if (!result) + { SDV_LOG_ERROR("[WinTunnel] SendData failed in underlying transport"); } return result; + } bool CWinTunnelConnection::AsyncConnect(/*in*/ sdv::IInterfaceAccess* pReceiver) @@ -70,7 +81,7 @@ bool CWinTunnelConnection::AsyncConnect(/*in*/ sdv::IInterfaceAccess* pReceiver) { std::lock_guard lock(m_CallbackMtx); sdv::TInterfaceAccessPtr acc(pReceiver); - m_pUpperReceiver = acc.GetInterface(); + m_pUpperRecv = acc.GetInterface(); m_pUpperEvent = acc.GetInterface(); } @@ -110,14 +121,8 @@ void CWinTunnelConnection::Disconnect() return; } + // Keep upper callbacks on plain Disconnect(); they are released in DestroyObject(). m_Transport->Disconnect(); - - // Clear upper-layer callbacks (thread-safe) - { - std::lock_guard lock(m_CallbackMtx); - m_pUpperReceiver = nullptr; - m_pUpperEvent = nullptr; - } } uint64_t CWinTunnelConnection::RegisterStateEventCallback( @@ -156,55 +161,91 @@ sdv::ipc::EConnectState CWinTunnelConnection::GetConnectState() const void CWinTunnelConnection::DestroyObject() { + bool expected = false; + if (!m_DestroyObjectCalled.compare_exchange_strong(expected, true)) + { + return; + } + + // Stop forwarding to upper layer before transport teardown. + { + std::lock_guard lock(m_CallbackMtx); + m_pUpperRecv = nullptr; + m_pUpperEvent = nullptr; + } + + // Do not notify upper layer during destruction teardown to avoid races. + SetConnectState(sdv::ipc::EConnectState::terminating); + Disconnect(); - std::lock_guard lock(m_CallbackMtx); - m_Transport.reset(); + { + std::lock_guard lock(m_CallbackMtx); + m_Transport.reset(); + } + + std::function removeCallback; + { + std::lock_guard watchdogLock(m_WatchdogMtx); + removeCallback = std::move(m_WatchdogRemoveCallback); + } + if (removeCallback) + { + removeCallback(this); + } } -void CWinTunnelConnection::ReceiveData( - /*inout*/ sdv::sequence>& seqData) +void CWinTunnelConnection::ReceiveData(/*inout*/ sdv::sequence>& seqData) { - // Expect at least one chunk (the tunnel header) + if (seqData.empty()) { SDV_LOG_ERROR("[WinTunnel] ReceiveData: empty sequence"); - return; // nothing to do + return; } const auto& hdrChunk = seqData[0]; if (hdrChunk.size() < sizeof(STunnelHeader)) { SDV_LOG_ERROR("[WinTunnel] ReceiveData: invalid tunnel header size"); - // Invalid tunnel frame; drop it for now (could set communication_error) return; } STunnelHeader hdr{}; std::memcpy(&hdr, hdrChunk.get(), sizeof(STunnelHeader)); + // Future demux point: + // if (hdr.uiChannelId != m_ChannelId) { ... } - // TODO: use channelId for multiplexing later - - // Build payload-only sequence: drop header chunk, keep others sdv::sequence> payload; for (size_t i = 1; i < seqData.size(); ++i) { payload.push_back(seqData[i]); } - if (m_pUpperReceiver) + sdv::ipc::IDataReceiveCallback* upper = nullptr; { - try { - m_pUpperReceiver->ReceiveData(payload); // header stripped - } catch (...) { - SDV_LOG_ERROR("[WinTunnel] Exception in upper receiver's ReceiveData"); + std::lock_guard lock(m_CallbackMtx); + upper = m_pUpperRecv; + } + + if (upper) + { + try + { + upper->ReceiveData(payload); + } + catch (...) + { + SDV_LOG_ERROR("[WinTunnel] Exception in upper receiver callback"); } } + } void CWinTunnelConnection::SetConnectState(sdv::ipc::EConnectState state) { sdv::ipc::IConnectEventCallback* upper = nullptr; + { std::lock_guard lock(m_CallbackMtx); upper = m_pUpperEvent; @@ -218,8 +259,7 @@ void CWinTunnelConnection::SetConnectState(sdv::ipc::EConnectState state) } catch (...) { - SDV_LOG_ERROR("[WinTunnel] Exception in upper event callback's SetConnectState"); - // Never let user callback crash the transport. + SDV_LOG_ERROR("[WinTunnel] Exception in upper event callback"); } } } diff --git a/sdv_services/uds_win_tunnel/connection.h b/sdv_services/uds_win_tunnel/connection.h index 5b0fcdd..7549dc8 100644 --- a/sdv_services/uds_win_tunnel/connection.h +++ b/sdv_services/uds_win_tunnel/connection.h @@ -21,10 +21,12 @@ #include #include #include +#include #include #include #include #include +#include #include "../sdv_services/uds_win_sockets/connection.h" // existing AF_UNIX transport: CWinsockConnection @@ -69,7 +71,7 @@ public: /** * @brief Destructor. */ - virtual ~CWinTunnelConnection() = default; + virtual ~CWinTunnelConnection(); BEGIN_SDV_INTERFACE_MAP() SDV_INTERFACE_ENTRY(sdv::ipc::IDataSend) @@ -153,15 +155,19 @@ public: // Helpers void SetChannelId(uint16_t channelId) { m_ChannelId = channelId; } uint16_t GetChannelId() const noexcept { return m_ChannelId; } + void SetWatchDogRemoveCallback(std::function callback); private: std::shared_ptr m_Transport; ///< shared physical tunnel port uint16_t m_ChannelId{0}; ///< default logical channel id // Upper layer callbacks (original VAPI receiver) - sdv::ipc::IDataReceiveCallback* m_pUpperReceiver{nullptr}; + sdv::ipc::IDataReceiveCallback* m_pUpperRecv{nullptr}; sdv::ipc::IConnectEventCallback* m_pUpperEvent{nullptr}; mutable std::mutex m_CallbackMtx; + std::mutex m_WatchdogMtx; + std::function m_WatchdogRemoveCallback; + std::atomic m_DestroyObjectCalled{false}; }; #endif // UDS_WIN_TUNNEL_CONNECTION_H diff --git a/sdv_services/uds_win_tunnel/watchdog.cpp b/sdv_services/uds_win_tunnel/watchdog.cpp new file mode 100644 index 0000000..83343ba --- /dev/null +++ b/sdv_services/uds_win_tunnel/watchdog.cpp @@ -0,0 +1,58 @@ +/******************************************************************************** +* Copyright (c) 2025-2026 ZF Friedrichshafen AG +* +* This program and the accompanying materials are made available under the +* terms of the Apache License Version 2.0 which is available at +* https://www.apache.org/licenses/LICENSE-2.0 +* +* SPDX-License-Identifier: Apache-2.0 +* +* Contributors: +* Denisa Ros - initial API and implementation +********************************************************************************/ + +#ifdef _WIN32 + +#include "watchdog.h" + +void CWinTunnelConnectionWatchDog::AddConnectionImpl(const std::shared_ptr& connection) +{ + std::lock_guard lock(m_Mutex); + m_Connections[connection.get()] = connection; +} + +void CWinTunnelConnectionWatchDog::RemoveConnection(const void* connection) +{ + if (!connection) + { + return; + } + + std::shared_ptr removed; + { + std::lock_guard lock(m_Mutex); + auto it = m_Connections.find(connection); + if (it == m_Connections.end()) + { + return; + } + + removed = std::move(it->second); + m_Connections.erase(it); + } + + removed.reset(); +} + +void CWinTunnelConnectionWatchDog::Clear() +{ + std::map> localConnections; + { + std::lock_guard lock(m_Mutex); + localConnections.swap(m_Connections); + } + + localConnections.clear(); +} + +#endif // _WIN32 diff --git a/sdv_services/uds_win_tunnel/watchdog.h b/sdv_services/uds_win_tunnel/watchdog.h new file mode 100644 index 0000000..c87d9ff --- /dev/null +++ b/sdv_services/uds_win_tunnel/watchdog.h @@ -0,0 +1,47 @@ +/******************************************************************************** +* Copyright (c) 2025-2026 ZF Friedrichshafen AG +* +* This program and the accompanying materials are made available under the +* terms of the Apache License Version 2.0 which is available at +* https://www.apache.org/licenses/LICENSE-2.0 +* +* SPDX-License-Identifier: Apache-2.0 +* +* Contributors: +* Denisa Ros - initial API and implementation +********************************************************************************/ + +#ifdef _WIN32 +#ifndef UDS_WIN_TUNNEL_WATCHDOG_H +#define UDS_WIN_TUNNEL_WATCHDOG_H + +#include +#include +#include + +class CWinTunnelConnectionWatchDog +{ +public: + template + void AddConnection(const std::shared_ptr& connection) + { + if (!connection) + { + return; + } + + AddConnectionImpl(std::static_pointer_cast(connection)); + } + + void RemoveConnection(const void* connection); + void Clear(); + +private: + void AddConnectionImpl(const std::shared_ptr& connection); + + std::mutex m_Mutex; + std::map> m_Connections; +}; + +#endif // UDS_WIN_TUNNEL_WATCHDOG_H +#endif // _WIN32 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index cc93e23..83abb2c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -60,6 +60,7 @@ endif() # Add test projects add_subdirectory(unit_tests/concurrency) add_subdirectory(unit_tests/flags_type) +add_subdirectory(unit_tests/unique_id) add_subdirectory(unit_tests/scheduler) add_subdirectory(unit_tests/idl_compiler) add_subdirectory(unit_tests/commandline_parser) @@ -68,16 +69,18 @@ add_subdirectory(unit_tests/asc_format) add_subdirectory(unit_tests/basic_types) add_subdirectory(unit_tests/smart_ifc) add_subdirectory(unit_tests/toml_parser) +add_subdirectory(unit_tests/simple_toml_parser) add_subdirectory(unit_tests/parameters) add_subdirectory(unit_tests/module_control) add_subdirectory(unit_tests/repository) +add_subdirectory(unit_tests/permission_control) add_subdirectory(unit_tests/shared_mem) add_subdirectory(unit_tests/memory_manager) add_subdirectory(unit_tests/core_loader) add_subdirectory(unit_tests/named_mutex) add_subdirectory(unit_tests/trace_fifo) add_subdirectory(unit_tests/socket_can_com_tests) -add_subdirectory(unit_tests/app_connect) +add_subdirectory(unit_tests/app_settings) add_subdirectory(unit_tests/process_control) add_subdirectory(unit_tests/ipc_com) add_subdirectory(unit_tests/ipc_connect) @@ -89,6 +92,7 @@ add_subdirectory(unit_tests/unix_sockets) add_subdirectory(unit_tests/unix_tunnel) add_subdirectory(unit_tests/win_sockets) add_subdirectory(unit_tests/win_tunnel) +add_subdirectory(component_tests/param_utils) add_subdirectory(component_tests/config) add_subdirectory(component_tests/logger) add_subdirectory(component_tests/data_dispatch_service) @@ -99,4 +103,5 @@ add_subdirectory(component_tests/vss_util) add_subdirectory(component_tests/task_timer) add_subdirectory(component_tests/app_control) add_subdirectory(component_tests/toml_parser) +add_subdirectory(component_tests/app_connect) add_subdirectory(manual_tests/silkit_can_com_tests) diff --git a/tests/cmake/run_gtest_serialized.cmake b/tests/cmake/run_gtest_serialized.cmake new file mode 100644 index 0000000..9a7fb79 --- /dev/null +++ b/tests/cmake/run_gtest_serialized.cmake @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.20) + +if(NOT DEFINED TEST_EXE) + message(FATAL_ERROR "TEST_EXE is required") +endif() + +if(NOT DEFINED TEST_XML) + message(FATAL_ERROR "TEST_XML is required") +endif() + +if(NOT DEFINED LOCK_FILE) + message(FATAL_ERROR "LOCK_FILE is required") +endif() + +if(NOT DEFINED TEST_EXECUTION_MODE) + set(TEST_EXECUTION_MODE "CMake") +endif() + +if(NOT DEFINED LOCK_TIMEOUT_SEC) + set(LOCK_TIMEOUT_SEC 1200) +endif() + +file(LOCK "${LOCK_FILE}" TIMEOUT ${LOCK_TIMEOUT_SEC} RESULT_VARIABLE LOCK_RESULT) +if(NOT LOCK_RESULT STREQUAL "0") + message(FATAL_ERROR "Failed to acquire test lock '${LOCK_FILE}': ${LOCK_RESULT}") +endif() + +execute_process( + COMMAND "${CMAKE_COMMAND}" -E env + "TEST_EXECUTION_MODE=${TEST_EXECUTION_MODE}" + "${TEST_EXE}" + "--gtest_output=xml:${TEST_XML}" + RESULT_VARIABLE TEST_RESULT +) + +file(LOCK "${LOCK_FILE}" RELEASE) + +if(NOT TEST_RESULT EQUAL 0) + message(FATAL_ERROR "Test execution failed with code ${TEST_RESULT}: ${TEST_EXE}") +endif() diff --git a/tests/component_tests/app_connect/CMakeLists.txt b/tests/component_tests/app_connect/CMakeLists.txt new file mode 100644 index 0000000..d80398a --- /dev/null +++ b/tests/component_tests/app_connect/CMakeLists.txt @@ -0,0 +1,156 @@ +#******************************************************************************* +# Copyright (c) 2025-2026 ZF Friedrichshafen AG +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# +# Contributors: +# Erik Verhoeven - initial API and implementation +#******************************************************************************* + +# Define project +project (ComponentTest_App_Connect VERSION 1.0 LANGUAGES CXX) + +# Find the IDL compiler +# REMARKS The compiler can only be found after it has build. Having both the compiler and the unittest project build, causes an +# error during the scanning phase of CMake. +#find_program(SDVIDL NAMES "sdv_idl_compiler" PATHS "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/../../bin/" NO_CACHE) +set (SDVIDL "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/../../bin/sdv_idl_compiler") +message("Use IDL compiler: ${SDVIDL}") + +# Compile the IDL +add_custom_command( + OUTPUT ${PROJECT_SOURCE_DIR}/generated/test_component.h ${PROJECT_SOURCE_DIR}/generated/ps/proxystub.cpp ${PROJECT_SOURCE_DIR}/generated/serdes/test_component_serdes.h + DEPENDS sdv_idl_compiler + MAIN_DEPENDENCY test_component.idl + COMMENT "Build test_component.idl" + COMMAND ${SDVIDL} "${PROJECT_SOURCE_DIR}/test_component.idl" "-I${PROJECT_SOURCE_DIR}/../../../export" "-O${PROJECT_SOURCE_DIR}/generated" + VERBATIM + ) + +set_source_files_properties(test_component_ps.cpp OBJECT_DEPENDS ${PROJECT_SOURCE_DIR}/generated/test_component.h) +set_source_files_properties(test_component_ps.cpp OBJECT_DEPENDS ${PROJECT_SOURCE_DIR}/generated/ps/proxystub.cpp) +set_source_files_properties(test_component_ps.cpp OBJECT_DEPENDS ${PROJECT_SOURCE_DIR}/generated/serdes/test_component_serdes.h) +set_source_files_properties(test_component_device.cpp OBJECT_DEPENDS ${PROJECT_SOURCE_DIR}/generated/test_component.h) +set_source_files_properties(test_component_basic_service.cpp OBJECT_DEPENDS ${PROJECT_SOURCE_DIR}/generated/test_component.h) +set_source_files_properties(test_component_complex_service.cpp OBJECT_DEPENDS ${PROJECT_SOURCE_DIR}/generated/test_component.h) +set_source_files_properties(test_component_system_service.cpp OBJECT_DEPENDS ${PROJECT_SOURCE_DIR}/generated/test_component.h) +set_source_files_properties(test_component_utility.cpp OBJECT_DEPENDS ${PROJECT_SOURCE_DIR}/generated/test_component.h) + +# Create the proxy stub dynamic library +add_library(AppConnect_Component_ps SHARED "test_component_ps.cpp") +set_target_properties(AppConnect_Component_ps PROPERTIES PREFIX "") +set_target_properties(AppConnect_Component_ps PROPERTIES SUFFIX ".sdv") + +# Create the device dynamic library +add_library(AppConnect_Component_Device SHARED "test_component_device.cpp") +set_target_properties(AppConnect_Component_Device PROPERTIES PREFIX "") +set_target_properties(AppConnect_Component_Device PROPERTIES SUFFIX ".sdv") + +# Create the basic service dynamic library +add_library(AppConnect_Component_BasicService SHARED "test_component_basic_service.cpp") +set_target_properties(AppConnect_Component_BasicService PROPERTIES PREFIX "") +set_target_properties(AppConnect_Component_BasicService PROPERTIES SUFFIX ".sdv") + +# Create the complex service dynamic library +add_library(AppConnect_Component_ComplexService SHARED "test_component_complex_service.cpp") +set_target_properties(AppConnect_Component_ComplexService PROPERTIES PREFIX "") +set_target_properties(AppConnect_Component_ComplexService PROPERTIES SUFFIX ".sdv") + +# Create the system service dynamic library +add_library(AppConnect_Component_SystemService SHARED "test_component_system_service.cpp") +set_target_properties(AppConnect_Component_SystemService PROPERTIES PREFIX "") +set_target_properties(AppConnect_Component_SystemService PROPERTIES SUFFIX ".sdv") + +# Create the utility dynamic library +add_library(AppConnect_Component_Utility SHARED "test_component_utility.cpp") +set_target_properties(AppConnect_Component_Utility PROPERTIES PREFIX "") +set_target_properties(AppConnect_Component_Utility PROPERTIES SUFFIX ".sdv") + +# Server instances: Install the device, system, basic and complex service, utility and prox/stub components in server instances +# shared memory: 195 and 196 +# UDS: 199 and 200 +# Tunnel-UDS: 203 and 204 +# Client instances: Install the proxy/stub components +# shared memory: 197 and 198 +# UDS: 201 and 202 +# Tunnel-UDS: 205 and 206 +add_custom_target(AppConnect_Component_Install + COMMAND "$" DIRECT_INSTALL AppConnect_Component --instance195 "AppConnect_Component_*.sdv" "-I$" --overwrite + COMMAND "$" DIRECT_INSTALL AppConnect_Component --instance196 "AppConnect_Component_*.sdv" "-I$" --overwrite + COMMAND "$" DIRECT_INSTALL AppConnect_Component_ps --instance197 "AppConnect_Component_ps.sdv" "-I$" --overwrite + COMMAND "$" DIRECT_INSTALL AppConnect_Component_ps --instance198 "AppConnect_Component_ps.sdv" "-I$" --overwrite + COMMAND "$" DIRECT_INSTALL AppConnect_Component --instance199 "AppConnect_Component_*.sdv" "-I$" --overwrite + COMMAND "$" DIRECT_INSTALL AppConnect_Component --instance200 "AppConnect_Component_*.sdv" "-I$" --overwrite + COMMAND "$" DIRECT_INSTALL AppConnect_Component_ps --instance201 "AppConnect_Component_ps.sdv" "-I$" --overwrite + COMMAND "$" DIRECT_INSTALL AppConnect_Component_ps --instance202 "AppConnect_Component_ps.sdv" "-I$" --overwrite + COMMAND "$" DIRECT_INSTALL AppConnect_Component --instance203 "AppConnect_Component_*.sdv" "-I$" --overwrite + COMMAND "$" DIRECT_INSTALL AppConnect_Component --instance204 "AppConnect_Component_*.sdv" "-I$" --overwrite + COMMAND "$" DIRECT_INSTALL AppConnect_Component_ps --instance205 "AppConnect_Component_ps.sdv" "-I$" --overwrite + COMMAND "$" DIRECT_INSTALL AppConnect_Component_ps --instance206 "AppConnect_Component_ps.sdv" "-I$" --overwrite + DEPENDS dependency_sdv_components AppConnect_Component_ps AppConnect_Component_Device AppConnect_Component_BasicService AppConnect_Component_ComplexService AppConnect_Component_SystemService AppConnect_Component_Utility + WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} +) + +# Configure listener for server instance +# Shared memory: 196 (195 is default) +# UDS: 199, 200 +# Tunnel-UDS: 203, 204 +# Connections for client instances +# Shared memory: 197 and 198 +# UDS: 201 and 202 +# Tunnel-UDS: 205 and 206 +add_custom_target(AppConnect_Communication_Install + COMMAND "$" LISTENER ADD MySpecialListener DefaultSharedMemory Name=private_channel_196_198 --instance196 + COMMAND "$" CONNECTION ADD DefaultChannelTo195 DefaultSharedMemory Name=DEFAULT_CHANNEL_195 --instance197 + COMMAND "$" CONNECTION ADD MySpecialConnection DefaultSharedMemory Name=private_channel_196_198 --instance198 + COMMAND "$" LISTENER ADD Default unix_domain_sockets Name=DEFAULT_CHANNEL_199 --instance199 + COMMAND "$" LISTENER ADD MySpecialListener unix_domain_sockets Name=private_channel_200_202 --instance200 + COMMAND "$" CONNECTION ADD DefaultChannelTo199 unix_domain_sockets Name=DEFAULT_CHANNEL_199 --instance201 + COMMAND "$" CONNECTION ADD MySpecialConnection unix_domain_sockets Name=private_channel_200_202 --instance202 + COMMAND "$" LISTENER ADD Default unix_domain_sockets_tunnel Name=DEFAULT_CHANNEL_203 --instance203 + COMMAND "$" LISTENER ADD MySpecialListener unix_domain_sockets_tunnel Name=private_channel_204_206 --instance204 + COMMAND "$" CONNECTION ADD DefaultChannelTo203 unix_domain_sockets_tunnel Name=DEFAULT_CHANNEL_203 --instance205 + COMMAND "$" CONNECTION ADD MySpecialConnection unix_domain_sockets_tunnel Name=private_channel_204_206 --instance206 + DEPENDS dependency_sdv_components AppConnect_Component_ps AppConnect_Component_Device AppConnect_Component_BasicService AppConnect_Component_ComplexService AppConnect_Component_SystemService AppConnect_Component_Utility + WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} +) + +# Compile the source code of the server +add_executable(AppConnect_Server + "main_server.cpp") +target_link_libraries(AppConnect_Server ${CMAKE_DL_LIBS}) + +# Compile the source code +add_executable(ComponentTest_App_Connect + "main.cpp" + "app_connect_tests_shared_mem.cpp" + "app_connect_tests_uds.cpp" + "app_connect_tests_uds_tunnel.cpp" + ) +target_link_libraries(ComponentTest_App_Connect ${CMAKE_DL_LIBS} GTest::GTest) + +# Add the connect test +add_test(NAME ComponentTest_App_Connect COMMAND ComponentTest_App_Connect) + +# Execute the test +add_custom_command(TARGET ComponentTest_App_Connect POST_BUILD + COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake "$" --gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ComponentTest_App_Connect.xml + VERBATIM +) + +# Build dependencies +add_dependencies(AppConnect_Server dependency_sdv_components) +add_dependencies(ComponentTest_App_Connect dependency_sdv_components) +add_dependencies(ComponentTest_App_Connect AppConnect_Server) +add_dependencies(ComponentTest_App_Connect AppConnect_Component_Install) +add_dependencies(ComponentTest_App_Connect AppConnect_Communication_Install) +add_dependencies(AppConnect_Component_ps dependency_sdv_components) +add_dependencies(AppConnect_Component_Device dependency_sdv_components) +add_dependencies(AppConnect_Component_BasicService dependency_sdv_components) +add_dependencies(AppConnect_Component_ComplexService dependency_sdv_components) +add_dependencies(AppConnect_Component_SystemService dependency_sdv_components) +add_dependencies(AppConnect_Component_Utility dependency_sdv_components) diff --git a/tests/component_tests/app_connect/app_connect_tests_shared_mem.cpp b/tests/component_tests/app_connect/app_connect_tests_shared_mem.cpp new file mode 100644 index 0000000..c2d3197 --- /dev/null +++ b/tests/component_tests/app_connect/app_connect_tests_shared_mem.cpp @@ -0,0 +1,519 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#include +#include "../../../sdv_services/process_control/process_control.h" +#include "../../../global/ipc_named_mutex.h" +#include "../../../global/exec_dir_helper.h" +#include "generated/test_component.h" +#include +#include +#include +#include + +// The test use the following instance numbers (all configured as main application): +// - instance 195 server without additional listener configured (channel: DEFAULT_CHANNEL_195) +// - instance 196 server with additional listener configured (channels: DEFAULT_CHANNEL_196 and private_channel_196_198) +// - instance 197 client with 195-default connection configured (channel: DEFAULT_CHANNEL_195) +// - instance 198 client with 196-special connection configured (channel: private_channel_196_198) + +TEST(AppConnect, ServerStart_DefaultSharedMemory) +{ + // The default instance + sdv::app::CAppControl control; + EXPECT_FALSE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); + EXPECT_EQ(control.GetInstanceID(), 0u); + + bool bResult = control.Startup(R"toml([Application] +Mode = "Main" +Instance = 195 +[Console] +RedirectMon = true +)toml"); + EXPECT_TRUE(bResult); + EXPECT_TRUE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::main); + EXPECT_EQ(control.GetInstanceID(), 195u); + + // Within the server instance all objects should be accessible. + // NOTE: The complex service is running isolated. + + // Test utility + SStages sStages{}; + std::string ss; + auto ptrUtilityHelloObject = sdv::core::CreateUtility("TestObject_HelloUtility"); + EXPECT_TRUE(ptrUtilityHelloObject); + auto pHelloObject = ptrUtilityHelloObject.GetInterface(); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_FALSE(sStages.bDevice); + EXPECT_FALSE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_FALSE(sStages.bSystemService); + EXPECT_TRUE(sStages.bUtility); + std::cout << "Utility reports:" << std::endl << ss; + + // Test system object + sStages = {}; + ss.clear(); + pHelloObject = sdv::core::GetObject("TestObject_SystemHelloService"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_FALSE(sStages.bDevice); + EXPECT_FALSE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_TRUE(sStages.bSystemService); + EXPECT_FALSE(sStages.bUtility); + std::cout << "System service reports:" << std::endl << ss; + + // Test device object - executes SayHello on Utility and System Service + sStages = {}; + ss.clear(); + pHelloObject = sdv::core::GetObject("TestObject_HelloDevice"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_TRUE(sStages.bDevice); + EXPECT_FALSE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_TRUE(sStages.bSystemService); + EXPECT_TRUE(sStages.bUtility); + std::cout << "Device reports:" << std::endl << ss; + + // Test basic service object - executes SayHello on Device, Utility and System Service + sStages = {}; + ss.clear(); + pHelloObject = sdv::core::GetObject("TestObject_BasicHelloService"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_TRUE(sStages.bDevice); + EXPECT_TRUE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_TRUE(sStages.bSystemService); + EXPECT_TRUE(sStages.bUtility); + std::cout << "Basic service reports:" << std::endl << ss; + + //// Test complex service object - executes SayHello on Complex Service, Device, Utility and System Service + //sStages = {}; + //ss.clear(); + //pHelloObject = sdv::core::GetObject("TestObject_ComplexHelloService"); + //EXPECT_TRUE(pHelloObject); + //if (pHelloObject) ss = pHelloObject->SayHello(sStages); + //EXPECT_FALSE(ss.empty()); + //EXPECT_TRUE(sStages.bDevice); + //EXPECT_TRUE(sStages.bBasicService); + //EXPECT_TRUE(sStages.bComplexService); + //EXPECT_TRUE(sStages.bSystemService); + //EXPECT_TRUE(sStages.bUtility); + //std::cout << "Basic service reports:" << std::endl << ss; + + control.Shutdown(); + EXPECT_FALSE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); + EXPECT_EQ(control.GetInstanceID(), 0u); +} + +TEST(AppConnect, ServerStart_PrivateSharedMemory) +{ + // The default instance + sdv::app::CAppControl control; + EXPECT_FALSE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); + EXPECT_EQ(control.GetInstanceID(), 0u); + + bool bResult = control.Startup(R"toml([Application] +Mode = "Main" +Instance = 196 +[Console] +RedirectMon = true +)toml"); + EXPECT_TRUE(bResult); + EXPECT_TRUE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::main); + EXPECT_EQ(control.GetInstanceID(), 196u); + + // Test utility + SStages sStages{}; + std::string ss; + auto ptrUtilityHelloObject = sdv::core::CreateUtility("TestObject_HelloUtility"); + EXPECT_TRUE(ptrUtilityHelloObject); + auto pHelloObject = ptrUtilityHelloObject.GetInterface(); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) + ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_FALSE(sStages.bDevice); + EXPECT_FALSE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_FALSE(sStages.bSystemService); + EXPECT_TRUE(sStages.bUtility); + std::cout << "Utility reports:" << std::endl << ss; + + // Test system object + sStages = {}; + ss.clear(); + pHelloObject = sdv::core::GetObject("TestObject_SystemHelloService"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) + ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_FALSE(sStages.bDevice); + EXPECT_FALSE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_TRUE(sStages.bSystemService); + EXPECT_FALSE(sStages.bUtility); + std::cout << "System service reports:" << std::endl << ss; + + // Test device object - executes SayHello on Utility and System Service + sStages = {}; + ss.clear(); + pHelloObject = sdv::core::GetObject("TestObject_HelloDevice"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) + ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_TRUE(sStages.bDevice); + EXPECT_FALSE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_TRUE(sStages.bSystemService); + EXPECT_TRUE(sStages.bUtility); + std::cout << "Device reports:" << std::endl << ss; + + // Test basic service object - executes SayHello on Device, Utility and System Service + sStages = {}; + ss.clear(); + pHelloObject = sdv::core::GetObject("TestObject_BasicHelloService"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) + ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_TRUE(sStages.bDevice); + EXPECT_TRUE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_TRUE(sStages.bSystemService); + EXPECT_TRUE(sStages.bUtility); + std::cout << "Basic service reports:" << std::endl << ss; + + //// Test complex service object - executes SayHello on Complex Service, Device, Utility and System Service + // sStages = {}; + // ss.clear(); + // pHelloObject = sdv::core::GetObject("TestObject_ComplexHelloService"); + // EXPECT_TRUE(pHelloObject); + // if (pHelloObject) ss = pHelloObject->SayHello(sStages); + // EXPECT_FALSE(ss.empty()); + // EXPECT_TRUE(sStages.bDevice); + // EXPECT_TRUE(sStages.bBasicService); + // EXPECT_TRUE(sStages.bComplexService); + // EXPECT_TRUE(sStages.bSystemService); + // EXPECT_TRUE(sStages.bUtility); + // std::cout << "Basic service reports:" << std::endl << ss; + + control.Shutdown(); + EXPECT_FALSE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); + EXPECT_EQ(control.GetInstanceID(), 0u); +} + +TEST(AppConnect, ServerApp_Control_SharedMem) +{ + // The lifetime of the server app is managed through a named mutex being available for locking. Lock the mutex + ipc::named_mutex mtx("app_connect_test_mutex"); + std::unique_lock lock(mtx); + + // Use the process control class to manage the server process + // Enable access bypass to allow its usage. + CProcessControl procctrl; + procctrl.EnableProcessControlAccessBypass(); + procctrl.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(procctrl.GetObjectState(), sdv::EObjectState::initialized); + + // Execute the server process + sdv::process::TProcessID tServerID = procctrl.Execute( + (GetExecDirectory() / "AppConnect_Server").generic_u8string(), {"195"}, sdv::process::EProcessRights::default_rights); + EXPECT_NE(tServerID, 0u); + + // Wait for 1 second + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + // Test for the running server + EXPECT_FALSE(procctrl.WaitForTerminate(tServerID, 1)); + + // Release the mutex; this should shutdown the server + lock.unlock(); + + // Wait for 1 second + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + // Test for the running server - should be terminated + EXPECT_TRUE(procctrl.WaitForTerminate(tServerID, 10000)); + + // Just in case termination + procctrl.Terminate(tServerID); + + // End process control + procctrl.Shutdown(); +} + +TEST(AppConnect, ClientConnect_DefaultSharedMem) +{ + // The lifetime of the server app is managed through a named mutex being available for locking. Lock the mutex + ipc::named_mutex mtx("app_connect_test_mutex"); + std::unique_lock lock(mtx); + + // Use the process control class to manage the server process + // Enable access bypass to allow its usage. + CProcessControl procctrl; + procctrl.EnableProcessControlAccessBypass(); + procctrl.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(procctrl.GetObjectState(), sdv::EObjectState::initialized); + + // Execute the server process + sdv::process::TProcessID tServerID = procctrl.Execute( + (GetExecDirectory() / "AppConnect_Server").generic_u8string(), {"195"}, sdv::process::EProcessRights::default_rights); + EXPECT_NE(tServerID, 0u); + + // Wait for 1 second + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + // Test for the running server + EXPECT_FALSE(procctrl.WaitForTerminate(tServerID, 1)); + + // Start the second system using the default instance. This simulates a client + sdv::app::CAppControl control; + EXPECT_FALSE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); + EXPECT_EQ(control.GetInstanceID(), 0u); + + // When startup succeeds an automatic connection to the server 195 should have been made. + bool bResult = control.Startup(R"toml([Application] +Mode = "Main" +Instance = 197 +[Console] +RedirectMon = true +)toml"); + EXPECT_TRUE(bResult); + + // Test utility - not allowed + SStages sStages{}; + std::string ss; + auto ptrUtilityHelloObject = sdv::core::CreateUtility("TestObject_HelloUtility"); + EXPECT_FALSE(ptrUtilityHelloObject); + + // Test system object - not allowed BUT CURRENTLY WORKING + sStages = {}; + ss.clear(); + auto pHelloObject = sdv::core::GetObject("TestObject_SystemHelloService"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) + ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_FALSE(sStages.bDevice); + EXPECT_FALSE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_TRUE(sStages.bSystemService); + EXPECT_FALSE(sStages.bUtility); + std::cout << "System service reports:" << std::endl << ss; + + //// Test device object - not allowed BUT CURRENTLY WORKING + //sStages = {}; + //ss.clear(); + //pHelloObject = sdv::core::GetObject("TestObject_HelloDevice"); + //EXPECT_TRUE(pHelloObject); + //if (pHelloObject) + // ss = pHelloObject->SayHello(sStages); + //EXPECT_FALSE(ss.empty()); + //EXPECT_TRUE(sStages.bDevice); + //EXPECT_FALSE(sStages.bBasicService); + //EXPECT_FALSE(sStages.bComplexService); + //EXPECT_TRUE(sStages.bSystemService); + //EXPECT_TRUE(sStages.bUtility); + //std::cout << "Device reports:" << std::endl << ss; + + // Test basic service object - tries to executes SayHello on Device, Utility and System Service, but not allowed + sStages = {}; + ss.clear(); + pHelloObject = sdv::core::GetObject("TestObject_BasicHelloService"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) + ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_FALSE(sStages.bDevice); + EXPECT_TRUE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_FALSE(sStages.bSystemService); + EXPECT_FALSE(sStages.bUtility); + std::cout << "Basic service reports:" << std::endl << ss; + + //// Test complex service object - executes SayHello on Complex Service, Device, Utility and System Service + // sStages = {}; + // ss.clear(); + // pHelloObject = sdv::core::GetObject("TestObject_ComplexHelloService"); + // EXPECT_TRUE(pHelloObject); + // if (pHelloObject) ss = pHelloObject->SayHello(sStages); + // EXPECT_FALSE(ss.empty()); + // EXPECT_TRUE(sStages.bDevice); + // EXPECT_TRUE(sStages.bBasicService); + // EXPECT_TRUE(sStages.bComplexService); + // EXPECT_TRUE(sStages.bSystemService); + // EXPECT_TRUE(sStages.bUtility); + // std::cout << "Basic service reports:" << std::endl << ss; + + control.Shutdown(); + EXPECT_FALSE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); + EXPECT_EQ(control.GetInstanceID(), 0u); + + // Release the mutex; this should shutdown the server + lock.unlock(); + + // Wait for 1 second + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + // Test for the running server - should be terminated + EXPECT_TRUE(procctrl.WaitForTerminate(tServerID, 10000)); + + // Just in case termination + procctrl.Terminate(tServerID); + + // End process control + procctrl.Shutdown(); +} + +TEST(AppConnect, ClientConnect_PrivateSharedMem) +{ + // The lifetime of the server app is managed through a named mutex being available for locking. Lock the mutex + ipc::named_mutex mtx("app_connect_test_mutex"); + std::unique_lock lock(mtx); + + // Use the process control class to manage the server process + // Enable access bypass to allow its usage. + CProcessControl procctrl; + procctrl.EnableProcessControlAccessBypass(); + procctrl.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(procctrl.GetObjectState(), sdv::EObjectState::initialized); + + // Execute the server process + sdv::process::TProcessID tServerID = procctrl.Execute( + (GetExecDirectory() / "AppConnect_Server").generic_u8string(), {"196"}, sdv::process::EProcessRights::default_rights); + EXPECT_NE(tServerID, 0u); + + // Wait for 1 second + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + // Test for the running server + EXPECT_FALSE(procctrl.WaitForTerminate(tServerID, 1)); + + // Start the second system using the default instance. This simulates a client + sdv::app::CAppControl control; + EXPECT_FALSE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); + EXPECT_EQ(control.GetInstanceID(), 0u); + + // When startup succeeds an automatic connection to the server 196 should have been made. + bool bResult = control.Startup(R"toml([Application] +Mode = "Main" +Instance = 198 +[Console] +RedirectMon = true +)toml"); + EXPECT_TRUE(bResult); + + // Test utility - not allowed + SStages sStages{}; + std::string ss; + auto ptrUtilityHelloObject = sdv::core::CreateUtility("TestObject_HelloUtility"); + EXPECT_FALSE(ptrUtilityHelloObject); + + // Test system object - not allowed BUT CURRENTLY WORKING + sStages = {}; + ss.clear(); + auto pHelloObject = sdv::core::GetObject("TestObject_SystemHelloService"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) + ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_FALSE(sStages.bDevice); + EXPECT_FALSE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_TRUE(sStages.bSystemService); + EXPECT_FALSE(sStages.bUtility); + std::cout << "System service reports:" << std::endl << ss; + + // Test device object - not allowed BUT CURRENTLY WORKING + //sStages = {}; + //ss.clear(); + //pHelloObject = sdv::core::GetObject("TestObject_HelloDevice"); + //EXPECT_TRUE(pHelloObject); + //if (pHelloObject) + // ss = pHelloObject->SayHello(sStages); + //EXPECT_FALSE(ss.empty()); + //EXPECT_TRUE(sStages.bDevice); + //EXPECT_FALSE(sStages.bBasicService); + //EXPECT_FALSE(sStages.bComplexService); + //EXPECT_TRUE(sStages.bSystemService); + //EXPECT_TRUE(sStages.bUtility); + //std::cout << "Device reports:" << std::endl << ss; + + // Test basic service object - executes SayHello on Device, Utility and System Service + sStages = {}; + ss.clear(); + pHelloObject = sdv::core::GetObject("TestObject_BasicHelloService"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) + ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_FALSE(sStages.bDevice); + EXPECT_TRUE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_FALSE(sStages.bSystemService); + EXPECT_FALSE(sStages.bUtility); + std::cout << "Basic service reports:" << std::endl << ss; + + //// Test complex service object - executes SayHello on Complex Service, Device, Utility and System Service + //sStages = {}; + //ss.clear(); + //pHelloObject = sdv::core::GetObject("TestObject_ComplexHelloService"); + //EXPECT_TRUE(pHelloObject); + //if (pHelloObject) ss = pHelloObject->SayHello(sStages); + //EXPECT_FALSE(ss.empty()); + //EXPECT_TRUE(sStages.bDevice); + //EXPECT_TRUE(sStages.bBasicService); + //EXPECT_TRUE(sStages.bComplexService); + //EXPECT_TRUE(sStages.bSystemService); + //EXPECT_TRUE(sStages.bUtility); + //std::cout << "Basic service reports:" << std::endl << ss; + + control.Shutdown(); + EXPECT_FALSE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); + EXPECT_EQ(control.GetInstanceID(), 0u); + + // Release the mutex; this should shutdown the server + lock.unlock(); + + // Wait for 1 second + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + // Test for the running server - should be terminated + EXPECT_TRUE(procctrl.WaitForTerminate(tServerID, 10000)); + + // Just in case termination + procctrl.Terminate(tServerID); + + // End process control + procctrl.Shutdown(); +} diff --git a/tests/component_tests/app_connect/app_connect_tests_uds.cpp b/tests/component_tests/app_connect/app_connect_tests_uds.cpp new file mode 100644 index 0000000..7fb5de9 --- /dev/null +++ b/tests/component_tests/app_connect/app_connect_tests_uds.cpp @@ -0,0 +1,519 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#include +#include "../../../sdv_services/process_control/process_control.h" +#include "../../../global/ipc_named_mutex.h" +#include "../../../global/exec_dir_helper.h" +#include "generated/test_component.h" +#include +#include +#include +#include + +// The test use the following instance numbers (all configured as main application): +// - instance 199 server without additional listener configured (channel: DEFAULT_CHANNEL_199) +// - instance 200 server with additional listener configured (channels: DEFAULT_CHANNEL_200 and private_channel_200_202) +// - instance 201 client with 199-default connection configured (channel: DEFAULT_CHANNEL_199) +// - instance 202 client with 200-special connection configured (channel: private_channel_200_202) + +TEST(AppConnect_UDS, ServerStart_DefaultUnixDomainSockets) +{ + // The default instance + sdv::app::CAppControl control; + EXPECT_FALSE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); + EXPECT_EQ(control.GetInstanceID(), 0u); + + bool bResult = control.Startup(R"toml([Application] +Mode = "Main" +Instance = 199 +[Console] +RedirectMon = true +)toml"); + EXPECT_TRUE(bResult); + EXPECT_TRUE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::main); + EXPECT_EQ(control.GetInstanceID(), 199u); + + // Within the server instance all objects should be accessible. + // NOTE: The complex service is running isolated. + + // Test utility + SStages sStages{}; + std::string ss; + auto ptrUtilityHelloObject = sdv::core::CreateUtility("TestObject_HelloUtility"); + EXPECT_TRUE(ptrUtilityHelloObject); + auto pHelloObject = ptrUtilityHelloObject.GetInterface(); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_FALSE(sStages.bDevice); + EXPECT_FALSE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_FALSE(sStages.bSystemService); + EXPECT_TRUE(sStages.bUtility); + std::cout << "Utility reports:" << std::endl << ss; + + // Test system object + sStages = {}; + ss.clear(); + pHelloObject = sdv::core::GetObject("TestObject_SystemHelloService"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_FALSE(sStages.bDevice); + EXPECT_FALSE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_TRUE(sStages.bSystemService); + EXPECT_FALSE(sStages.bUtility); + std::cout << "System service reports:" << std::endl << ss; + + // Test device object - executes SayHello on Utility and System Service + sStages = {}; + ss.clear(); + pHelloObject = sdv::core::GetObject("TestObject_HelloDevice"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_TRUE(sStages.bDevice); + EXPECT_FALSE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_TRUE(sStages.bSystemService); + EXPECT_TRUE(sStages.bUtility); + std::cout << "Device reports:" << std::endl << ss; + + // Test basic service object - executes SayHello on Device, Utility and System Service + sStages = {}; + ss.clear(); + pHelloObject = sdv::core::GetObject("TestObject_BasicHelloService"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_TRUE(sStages.bDevice); + EXPECT_TRUE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_TRUE(sStages.bSystemService); + EXPECT_TRUE(sStages.bUtility); + std::cout << "Basic service reports:" << std::endl << ss; + + //// Test complex service object - executes SayHello on Complex Service, Device, Utility and System Service + //sStages = {}; + //ss.clear(); + //pHelloObject = sdv::core::GetObject("TestObject_ComplexHelloService"); + //EXPECT_TRUE(pHelloObject); + //if (pHelloObject) ss = pHelloObject->SayHello(sStages); + //EXPECT_FALSE(ss.empty()); + //EXPECT_TRUE(sStages.bDevice); + //EXPECT_TRUE(sStages.bBasicService); + //EXPECT_TRUE(sStages.bComplexService); + //EXPECT_TRUE(sStages.bSystemService); + //EXPECT_TRUE(sStages.bUtility); + //std::cout << "Basic service reports:" << std::endl << ss; + + control.Shutdown(); + EXPECT_FALSE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); + EXPECT_EQ(control.GetInstanceID(), 0u); +} + +TEST(AppConnect_UDS, ServerStart_PrivateUnixDomainSockets) +{ + // The default instance + sdv::app::CAppControl control; + EXPECT_FALSE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); + EXPECT_EQ(control.GetInstanceID(), 0u); + + bool bResult = control.Startup(R"toml([Application] +Mode = "Main" +Instance = 200 +[Console] +RedirectMon = true +)toml"); + EXPECT_TRUE(bResult); + EXPECT_TRUE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::main); + EXPECT_EQ(control.GetInstanceID(), 200u); + + // Test utility + SStages sStages{}; + std::string ss; + auto ptrUtilityHelloObject = sdv::core::CreateUtility("TestObject_HelloUtility"); + EXPECT_TRUE(ptrUtilityHelloObject); + auto pHelloObject = ptrUtilityHelloObject.GetInterface(); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) + ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_FALSE(sStages.bDevice); + EXPECT_FALSE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_FALSE(sStages.bSystemService); + EXPECT_TRUE(sStages.bUtility); + std::cout << "Utility reports:" << std::endl << ss; + + // Test system object + sStages = {}; + ss.clear(); + pHelloObject = sdv::core::GetObject("TestObject_SystemHelloService"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) + ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_FALSE(sStages.bDevice); + EXPECT_FALSE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_TRUE(sStages.bSystemService); + EXPECT_FALSE(sStages.bUtility); + std::cout << "System service reports:" << std::endl << ss; + + // Test device object - executes SayHello on Utility and System Service + sStages = {}; + ss.clear(); + pHelloObject = sdv::core::GetObject("TestObject_HelloDevice"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) + ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_TRUE(sStages.bDevice); + EXPECT_FALSE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_TRUE(sStages.bSystemService); + EXPECT_TRUE(sStages.bUtility); + std::cout << "Device reports:" << std::endl << ss; + + // Test basic service object - executes SayHello on Device, Utility and System Service + sStages = {}; + ss.clear(); + pHelloObject = sdv::core::GetObject("TestObject_BasicHelloService"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) + ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_TRUE(sStages.bDevice); + EXPECT_TRUE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_TRUE(sStages.bSystemService); + EXPECT_TRUE(sStages.bUtility); + std::cout << "Basic service reports:" << std::endl << ss; + + //// Test complex service object - executes SayHello on Complex Service, Device, Utility and System Service + // sStages = {}; + // ss.clear(); + // pHelloObject = sdv::core::GetObject("TestObject_ComplexHelloService"); + // EXPECT_TRUE(pHelloObject); + // if (pHelloObject) ss = pHelloObject->SayHello(sStages); + // EXPECT_FALSE(ss.empty()); + // EXPECT_TRUE(sStages.bDevice); + // EXPECT_TRUE(sStages.bBasicService); + // EXPECT_TRUE(sStages.bComplexService); + // EXPECT_TRUE(sStages.bSystemService); + // EXPECT_TRUE(sStages.bUtility); + // std::cout << "Basic service reports:" << std::endl << ss; + + control.Shutdown(); + EXPECT_FALSE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); + EXPECT_EQ(control.GetInstanceID(), 0u); +} + +TEST(AppConnect_UDS, ServerApp_Control_UnixDomainSockets) +{ + // The lifetime of the server app is managed through a named mutex being available for locking. Lock the mutex + ipc::named_mutex mtx("app_connect_test_mutex"); + std::unique_lock lock(mtx); + + // Use the process control class to manage the server process + // Enable access bypass to allow its usage. + CProcessControl procctrl; + procctrl.EnableProcessControlAccessBypass(); + procctrl.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(procctrl.GetObjectState(), sdv::EObjectState::initialized); + + // Execute the server process + sdv::process::TProcessID tServerID = procctrl.Execute( + (GetExecDirectory() / "AppConnect_Server").generic_u8string(), {"199"}, sdv::process::EProcessRights::default_rights); + EXPECT_NE(tServerID, 0u); + + // Wait for 1 second + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + // Test for the running server + EXPECT_FALSE(procctrl.WaitForTerminate(tServerID, 1)); + + // Release the mutex; this should shutdown the server + lock.unlock(); + + // Wait for 1 second + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + // Test for the running server - should be terminated + EXPECT_TRUE(procctrl.WaitForTerminate(tServerID, 10000)); + + // Just in case termination + procctrl.Terminate(tServerID); + + // End process control + procctrl.Shutdown(); +} + +TEST(AppConnect_UDS, ClientConnect_DefaultUnixDomainSockets) +{ + // The lifetime of the server app is managed through a named mutex being available for locking. Lock the mutex + ipc::named_mutex mtx("app_connect_test_mutex"); + std::unique_lock lock(mtx); + + // Use the process control class to manage the server process + // Enable access bypass to allow its usage. + CProcessControl procctrl; + procctrl.EnableProcessControlAccessBypass(); + procctrl.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(procctrl.GetObjectState(), sdv::EObjectState::initialized); + + // Execute the server process + sdv::process::TProcessID tServerID = procctrl.Execute( + (GetExecDirectory() / "AppConnect_Server").generic_u8string(), {"199"}, sdv::process::EProcessRights::default_rights); + EXPECT_NE(tServerID, 0u); + + // Wait for 1 second + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + // Test for the running server + EXPECT_FALSE(procctrl.WaitForTerminate(tServerID, 1)); + + // Start the second system using the default instance. This simulates a client + sdv::app::CAppControl control; + EXPECT_FALSE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); + EXPECT_EQ(control.GetInstanceID(), 0u); + + // When startup succeeds an automatic connection to the server 199 should have been made. + bool bResult = control.Startup(R"toml([Application] +Mode = "Main" +Instance = 201 +[Console] +RedirectMon = true +)toml"); + EXPECT_TRUE(bResult); + + // Test utility - not allowed + SStages sStages{}; + std::string ss; + auto ptrUtilityHelloObject = sdv::core::CreateUtility("TestObject_HelloUtility"); + EXPECT_FALSE(ptrUtilityHelloObject); + + // Test system object - not allowed BUT CURRENTLY WORKING + sStages = {}; + ss.clear(); + auto pHelloObject = sdv::core::GetObject("TestObject_SystemHelloService"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) + ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_FALSE(sStages.bDevice); + EXPECT_FALSE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_TRUE(sStages.bSystemService); + EXPECT_FALSE(sStages.bUtility); + std::cout << "System service reports:" << std::endl << ss; + + //// Test device object - not allowed BUT CURRENTLY WORKING + //sStages = {}; + //ss.clear(); + //pHelloObject = sdv::core::GetObject("TestObject_HelloDevice"); + //EXPECT_TRUE(pHelloObject); + //if (pHelloObject) + // ss = pHelloObject->SayHello(sStages); + //EXPECT_FALSE(ss.empty()); + //EXPECT_TRUE(sStages.bDevice); + //EXPECT_FALSE(sStages.bBasicService); + //EXPECT_FALSE(sStages.bComplexService); + //EXPECT_TRUE(sStages.bSystemService); + //EXPECT_TRUE(sStages.bUtility); + //std::cout << "Device reports:" << std::endl << ss; + + // Test basic service object - tries to executes SayHello on Device, Utility and System Service, but not allowed + sStages = {}; + ss.clear(); + pHelloObject = sdv::core::GetObject("TestObject_BasicHelloService"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) + ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_FALSE(sStages.bDevice); + EXPECT_TRUE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_FALSE(sStages.bSystemService); + EXPECT_FALSE(sStages.bUtility); + std::cout << "Basic service reports:" << std::endl << ss; + + //// Test complex service object - executes SayHello on Complex Service, Device, Utility and System Service + // sStages = {}; + // ss.clear(); + // pHelloObject = sdv::core::GetObject("TestObject_ComplexHelloService"); + // EXPECT_TRUE(pHelloObject); + // if (pHelloObject) ss = pHelloObject->SayHello(sStages); + // EXPECT_FALSE(ss.empty()); + // EXPECT_TRUE(sStages.bDevice); + // EXPECT_TRUE(sStages.bBasicService); + // EXPECT_TRUE(sStages.bComplexService); + // EXPECT_TRUE(sStages.bSystemService); + // EXPECT_TRUE(sStages.bUtility); + // std::cout << "Basic service reports:" << std::endl << ss; + + control.Shutdown(); + EXPECT_FALSE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); + EXPECT_EQ(control.GetInstanceID(), 0u); + + // Release the mutex; this should shutdown the server + lock.unlock(); + + // Wait for 1 second + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + // Test for the running server - should be terminated + EXPECT_TRUE(procctrl.WaitForTerminate(tServerID, 10000)); + + // Just in case termination + procctrl.Terminate(tServerID); + + // End process control + procctrl.Shutdown(); +} + +TEST(AppConnect_UDS, ClientConnect_PrivateUnixDomainSockets) +{ + // The lifetime of the server app is managed through a named mutex being available for locking. Lock the mutex + ipc::named_mutex mtx("app_connect_test_mutex"); + std::unique_lock lock(mtx); + + // Use the process control class to manage the server process + // Enable access bypass to allow its usage. + CProcessControl procctrl; + procctrl.EnableProcessControlAccessBypass(); + procctrl.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(procctrl.GetObjectState(), sdv::EObjectState::initialized); + + // Execute the server process + sdv::process::TProcessID tServerID = procctrl.Execute( + (GetExecDirectory() / "AppConnect_Server").generic_u8string(), {"200"}, sdv::process::EProcessRights::default_rights); + EXPECT_NE(tServerID, 0u); + + // Wait for 1 second + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + // Test for the running server + EXPECT_FALSE(procctrl.WaitForTerminate(tServerID, 1)); + + // Start the second system using the default instance. This simulates a client + sdv::app::CAppControl control; + EXPECT_FALSE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); + EXPECT_EQ(control.GetInstanceID(), 0u); + + // When startup succeeds an automatic connection to the server 200 should have been made. + bool bResult = control.Startup(R"toml([Application] +Mode = "Main" +Instance = 202 +[Console] +RedirectMon = true +)toml"); + EXPECT_TRUE(bResult); + + // Test utility - not allowed + SStages sStages{}; + std::string ss; + auto ptrUtilityHelloObject = sdv::core::CreateUtility("TestObject_HelloUtility"); + EXPECT_FALSE(ptrUtilityHelloObject); + + // Test system object - not allowed BUT CURRENTLY WORKING + sStages = {}; + ss.clear(); + auto pHelloObject = sdv::core::GetObject("TestObject_SystemHelloService"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) + ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_FALSE(sStages.bDevice); + EXPECT_FALSE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_TRUE(sStages.bSystemService); + EXPECT_FALSE(sStages.bUtility); + std::cout << "System service reports:" << std::endl << ss; + + //// Test device object - not allowed BUT CURRENTLY WORKING + //sStages = {}; + //ss.clear(); + //pHelloObject = sdv::core::GetObject("TestObject_HelloDevice"); + //EXPECT_TRUE(pHelloObject); + //if (pHelloObject) + // ss = pHelloObject->SayHello(sStages); + //EXPECT_FALSE(ss.empty()); + //EXPECT_TRUE(sStages.bDevice); + //EXPECT_FALSE(sStages.bBasicService); + //EXPECT_FALSE(sStages.bComplexService); + //EXPECT_TRUE(sStages.bSystemService); + //EXPECT_TRUE(sStages.bUtility); + //std::cout << "Device reports:" << std::endl << ss; + + // Test basic service object - tries to executes SayHello on Device, Utility and System Service, but not allowed + sStages = {}; + ss.clear(); + pHelloObject = sdv::core::GetObject("TestObject_BasicHelloService"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) + ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_FALSE(sStages.bDevice); + EXPECT_TRUE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_FALSE(sStages.bSystemService); + EXPECT_FALSE(sStages.bUtility); + std::cout << "Basic service reports:" << std::endl << ss; + + //// Test complex service object - executes SayHello on Complex Service, Device, Utility and System Service + // sStages = {}; + // ss.clear(); + // pHelloObject = sdv::core::GetObject("TestObject_ComplexHelloService"); + // EXPECT_TRUE(pHelloObject); + // if (pHelloObject) ss = pHelloObject->SayHello(sStages); + // EXPECT_FALSE(ss.empty()); + // EXPECT_TRUE(sStages.bDevice); + // EXPECT_TRUE(sStages.bBasicService); + // EXPECT_TRUE(sStages.bComplexService); + // EXPECT_TRUE(sStages.bSystemService); + // EXPECT_TRUE(sStages.bUtility); + // std::cout << "Basic service reports:" << std::endl << ss; + + control.Shutdown(); + EXPECT_FALSE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); + EXPECT_EQ(control.GetInstanceID(), 0u); + + // Release the mutex; this should shutdown the server + lock.unlock(); + + // Wait for 1 second + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + // Test for the running server - should be terminated + EXPECT_TRUE(procctrl.WaitForTerminate(tServerID, 10000)); + + // Just in case termination + procctrl.Terminate(tServerID); + + // End process control + procctrl.Shutdown(); +} diff --git a/tests/component_tests/app_connect/app_connect_tests_uds_tunnel.cpp b/tests/component_tests/app_connect/app_connect_tests_uds_tunnel.cpp new file mode 100644 index 0000000..2fec0dc --- /dev/null +++ b/tests/component_tests/app_connect/app_connect_tests_uds_tunnel.cpp @@ -0,0 +1,557 @@ +/******************************************************************************** + * Copyright (c) 2065-2066 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Denisa Ros - initial API and implementation + ********************************************************************************/ + +#include +#include "../../../sdv_services/process_control/process_control.h" +#include "../../../global/ipc_named_mutex.h" +#include "../../../global/exec_dir_helper.h" +#include "generated/test_component.h" +#include +#include +#include +#include +#include +#include + +namespace +{ +void ExpectConfiguredProvider(uint32_t instanceId, const std::string& configName, bool isListener, const std::string& expectedProvider) +{ + auto* pConnections = sdv::core::GetObject("AppSettingsService"); + if (pConnections) + { + const sdv::u8string cfg = isListener ? pConnections->GetListenerConfig(configName) + : pConnections->GetConnectionConfig(configName); + ASSERT_FALSE(cfg.empty()) << "Missing IPC config '" << configName << "'"; + + const std::string cfgStr = cfg; + EXPECT_NE(cfgStr.find("Name = \"" + expectedProvider + "\""), std::string::npos) + << "Unexpected provider config for '" << configName << "':\n" << cfgStr; + return; + } + + // In main app mode this service is not guaranteed to be directly accessible via repository object lookup. + // Fallback: validate the installed instance settings file where listener/connection definitions are persisted. + std::filesystem::path installDir; + if (const char* pInstall = std::getenv("SDV_COMPONENT_INSTALL")) + installDir = pInstall; + else + installDir = (GetExecDirectory() / ".." / ".." / "bin").lexically_normal(); + + const auto settingsPath = installDir / std::to_string(instanceId) / "settings.toml"; + std::ifstream settingsStream(settingsPath); + ASSERT_TRUE(settingsStream.is_open()) << "Cannot open settings file: " << settingsPath.generic_u8string(); + + std::stringstream buffer; + buffer << settingsStream.rdbuf(); + const std::string content = buffer.str(); + + const std::string blockStart = isListener ? "[[Settings.Listener]]" : "[[Settings.Connection]]"; + const std::string providerSection = isListener ? "[Settings.Listener.Provider]" : "[Settings.Connection.Provider]"; + const std::string expectedName = "Name = \"" + configName + "\""; + const std::string expectedProviderName = "Name = \"" + expectedProvider + "\""; + + size_t pos = content.find(blockStart); + while (pos != std::string::npos) + { + const size_t nextPos = content.find(blockStart, pos + blockStart.size()); + const std::string block = content.substr(pos, (nextPos == std::string::npos) ? std::string::npos : nextPos - pos); + if (block.find(expectedName) != std::string::npos) + { + EXPECT_NE(block.find(providerSection), std::string::npos) + << "Missing provider section in config block for '" << configName << "'"; + EXPECT_NE(block.find(expectedProviderName), std::string::npos) + << "Unexpected provider in settings block for '" << configName << "':\n" << block; + return; + } + pos = nextPos; + } + + FAIL() << "Missing IPC config block '" << configName << "' in " << settingsPath.generic_u8string(); +} +} // namespace + + +// The test use the following instance numbers (all configured as main application): +// - instance 203 server without additional listener configured (channel: DEFAULT_CHANNEL_203) +// - instance 204 server with additional listener configured (channels: private_channel_204_206) +// - instance 205 client with 203-default connection configured (channel: DEFAULT_CHANNEL_203) +// - instance 206 client with 204-special connection configured (channel: private_channel_204_206) +TEST(AppConnect_UDS_Tunnel, ServerStart_DefaultUDS_Tunnel) +{ + // The default instance + sdv::app::CAppControl control; + EXPECT_FALSE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); + EXPECT_EQ(control.GetInstanceID(), 0u); + + bool bResult = control.Startup(R"toml([Application] +Mode = "Main" +Instance = 203 +[Console] +RedirectMon = true +)toml"); + + EXPECT_TRUE(bResult); + EXPECT_TRUE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::main); + EXPECT_EQ(control.GetInstanceID(), 203u); + + // Test utility + SStages sStages{}; + std::string ss; + auto ptrUtilityHelloObject = sdv::core::CreateUtility("TestObject_HelloUtility"); + EXPECT_TRUE(ptrUtilityHelloObject); + auto pHelloObject = ptrUtilityHelloObject.GetInterface(); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) + ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_FALSE(sStages.bDevice); + EXPECT_FALSE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_FALSE(sStages.bSystemService); + EXPECT_TRUE(sStages.bUtility); + std::cout << "Utility reports:" << std::endl << ss; + + // Test system object + sStages = {}; + ss.clear(); + pHelloObject = sdv::core::GetObject("TestObject_SystemHelloService"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) + ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_FALSE(sStages.bDevice); + EXPECT_FALSE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_TRUE(sStages.bSystemService); + EXPECT_FALSE(sStages.bUtility); + std::cout << "System service reports:" << std::endl << ss; + + // Test device object - executes SayHello on Utility and System Service + sStages = {}; + ss.clear(); + pHelloObject = sdv::core::GetObject("TestObject_HelloDevice"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) + ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_TRUE(sStages.bDevice); + EXPECT_FALSE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_TRUE(sStages.bSystemService); + EXPECT_TRUE(sStages.bUtility); + std::cout << "Device reports:" << std::endl << ss; + + // Test basic service object - executes SayHello on Device, Utility and System Service + sStages = {}; + ss.clear(); + pHelloObject = sdv::core::GetObject("TestObject_BasicHelloService"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) + ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_TRUE(sStages.bDevice); + EXPECT_TRUE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_TRUE(sStages.bSystemService); + EXPECT_TRUE(sStages.bUtility); + std::cout << "Basic service reports:" << std::endl << ss; + + control.Shutdown(); + EXPECT_FALSE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); + EXPECT_EQ(control.GetInstanceID(), 0u); +} + +TEST(AppConnect_UDS_Tunnel, ServerStart_PrivateUDS_Tunnel) +{ + // The default instance + sdv::app::CAppControl control; + EXPECT_FALSE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); + EXPECT_EQ(control.GetInstanceID(), 0u); + + bool bResult = control.Startup(R"toml([Application] +Mode = "Main" +Instance = 204 +[Console] +RedirectMon = true +)toml"); + + EXPECT_TRUE(bResult); + EXPECT_TRUE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::main); + EXPECT_EQ(control.GetInstanceID(), 204u); + ExpectConfiguredProvider(204u, "MySpecialListener", true, "unix_domain_sockets_tunnel"); + + // Test utility + SStages sStages{}; + std::string ss; + auto ptrUtilityHelloObject = sdv::core::CreateUtility("TestObject_HelloUtility"); + EXPECT_TRUE(ptrUtilityHelloObject); + auto pHelloObject = ptrUtilityHelloObject.GetInterface(); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) + ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_FALSE(sStages.bDevice); + EXPECT_FALSE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_FALSE(sStages.bSystemService); + EXPECT_TRUE(sStages.bUtility); + std::cout << "Utility reports:" << std::endl << ss; + + // Test system object + sStages = {}; + ss.clear(); + pHelloObject = sdv::core::GetObject("TestObject_SystemHelloService"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) + ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_FALSE(sStages.bDevice); + EXPECT_FALSE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_TRUE(sStages.bSystemService); + EXPECT_FALSE(sStages.bUtility); + std::cout << "System service reports:" << std::endl << ss; + + // Test device object - executes SayHello on Utility and System Service + sStages = {}; + ss.clear(); + pHelloObject = sdv::core::GetObject("TestObject_HelloDevice"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) + ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_TRUE(sStages.bDevice); + EXPECT_FALSE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_TRUE(sStages.bSystemService); + EXPECT_TRUE(sStages.bUtility); + std::cout << "Device reports:" << std::endl << ss; + + // Test basic service object - executes SayHello on Device, Utility and System Service + sStages = {}; + ss.clear(); + pHelloObject = sdv::core::GetObject("TestObject_BasicHelloService"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) + ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_TRUE(sStages.bDevice); + EXPECT_TRUE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_TRUE(sStages.bSystemService); + EXPECT_TRUE(sStages.bUtility); + std::cout << "Basic service reports:" << std::endl << ss; + + //// Test complex service object - executes SayHello on Complex Service, Device, Utility and System Service + // sStages = {}; + // ss.clear(); + // pHelloObject = sdv::core::GetObject("TestObject_ComplexHelloService"); + // EXPECT_TRUE(pHelloObject); + // if (pHelloObject) ss = pHelloObject->SayHello(sStages); + // EXPECT_FALSE(ss.empty()); + // EXPECT_TRUE(sStages.bDevice); + // EXPECT_TRUE(sStages.bBasicService); + // EXPECT_TRUE(sStages.bComplexService); + // EXPECT_TRUE(sStages.bSystemService); + // EXPECT_TRUE(sStages.bUtility); + // std::cout << "Basic service reports:" << std::endl << ss; + + control.Shutdown(); + EXPECT_FALSE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); + EXPECT_EQ(control.GetInstanceID(), 0u); +} + +TEST(AppConnect_UDS_Tunnel, ServerApp_Control_UDS_Tunnel) +{ + // The lifetime of the server app is managed through a named mutex being available for locking. Lock the mutex + ipc::named_mutex mtx("app_connect_tunnel_test_mutex"); + std::unique_lock lock(mtx); + + // Use the process control class to manage the server process + // Enable access bypass to allow its usage. + CProcessControl procctrl; + procctrl.EnableProcessControlAccessBypass(); + procctrl.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(procctrl.GetObjectState(), sdv::EObjectState::initialized); + + // Execute the server process + sdv::process::TProcessID tServerID = procctrl.Execute( + (GetExecDirectory() / "AppConnect_Server").generic_u8string(), {"203"}, sdv::process::EProcessRights::default_rights); + EXPECT_NE(tServerID, 0u); + + // Wait for 1 second + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + // Test for the running server + EXPECT_FALSE(procctrl.WaitForTerminate(tServerID, 1)); + + // Release the mutex; this should shutdown the server + lock.unlock(); + + // Wait for 1 second + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + // Test for the running server - should be terminated + EXPECT_TRUE(procctrl.WaitForTerminate(tServerID, 10000)); + + // Just in case termination + procctrl.Terminate(tServerID); + + // End process control + procctrl.Shutdown(); +} + +TEST(AppConnect_UDS_Tunnel, ClientConnect_DefaultUDS_Tunnel) +{ + // The lifetime of the server app is managed through a named mutex being available for locking. Lock the mutex + ipc::named_mutex mtx("app_connect_tunnel_test_mutex"); + std::unique_lock lock(mtx); + + // Use the process control class to manage the server process + // Enable access bypass to allow its usage. + CProcessControl procctrl; + procctrl.EnableProcessControlAccessBypass(); + procctrl.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(procctrl.GetObjectState(), sdv::EObjectState::initialized); + + // Execute the server process + sdv::process::TProcessID tServerID = procctrl.Execute( + (GetExecDirectory() / "AppConnect_Server").generic_u8string(), {"203"}, sdv::process::EProcessRights::default_rights); + EXPECT_NE(tServerID, 0u); + + // Wait for 1 second + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + // Test for the running server + EXPECT_FALSE(procctrl.WaitForTerminate(tServerID, 1)); + + // Start the second system using the default instance. This simulates a client + sdv::app::CAppControl control; + EXPECT_FALSE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); + EXPECT_EQ(control.GetInstanceID(), 0u); + + // When startup succeeds an automatic connection to the server 203 should have been made. + bool bResult = control.Startup(R"toml([Application] +Mode = "Main" +Instance = 205 +[Console] +RedirectMon = true +)toml"); + EXPECT_TRUE(bResult); + ExpectConfiguredProvider(205u, "DefaultChannelTo203", false, "unix_domain_sockets_tunnel"); + + // Test utility - not allowed + SStages sStages{}; + std::string ss; + auto ptrUtilityHelloObject = sdv::core::CreateUtility("TestObject_HelloUtility"); + EXPECT_FALSE(ptrUtilityHelloObject); + + // Test system object - not allowed BUT CURRENTLY WORKING + sStages = {}; + ss.clear(); + auto pHelloObject = sdv::core::GetObject("TestObject_SystemHelloService"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) + ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_FALSE(sStages.bDevice); + EXPECT_FALSE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_TRUE(sStages.bSystemService); + EXPECT_FALSE(sStages.bUtility); + std::cout << "System service reports:" << std::endl << ss; + + //// Test device object - not allowed BUT CURRENTLY WORKING + //sStages = {}; + //ss.clear(); + //pHelloObject = sdv::core::GetObject("TestObject_HelloDevice"); + //EXPECT_TRUE(pHelloObject); + //if (pHelloObject) + // ss = pHelloObject->SayHello(sStages); + //EXPECT_FALSE(ss.empty()); + //EXPECT_TRUE(sStages.bDevice); + //EXPECT_FALSE(sStages.bBasicService); + //EXPECT_FALSE(sStages.bComplexService); + //EXPECT_TRUE(sStages.bSystemService); + //EXPECT_TRUE(sStages.bUtility); + //std::cout << "Device reports:" << std::endl << ss; + + // Test basic service object - tries to executes SayHello on Device, Utility and System Service, but not allowed + sStages = {}; + ss.clear(); + pHelloObject = sdv::core::GetObject("TestObject_BasicHelloService"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) + ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_FALSE(sStages.bDevice); + EXPECT_TRUE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_FALSE(sStages.bSystemService); + EXPECT_FALSE(sStages.bUtility); + std::cout << "Basic service reports:" << std::endl << ss; + + control.Shutdown(); + EXPECT_FALSE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); + EXPECT_EQ(control.GetInstanceID(), 0u); + + // Release the mutex; this should shutdown the server + lock.unlock(); + + // Wait for 1 second + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + // Test for the running server - should be terminated + EXPECT_TRUE(procctrl.WaitForTerminate(tServerID, 10000)); + + // Just in case termination + procctrl.Terminate(tServerID); + + // End process control + procctrl.Shutdown(); +} + +TEST(AppConnect_UDS_Tunnel, ClientConnect_PrivateUDS_Tunnel) +{ + // The lifetime of the server app is managed through a named mutex being available for locking. Lock the mutex + ipc::named_mutex mtx("app_connect_tunnel_test_mutex"); + std::unique_lock lock(mtx); + + // Use the process control class to manage the server process + // Enable access bypass to allow its usage. + CProcessControl procctrl; + procctrl.EnableProcessControlAccessBypass(); + procctrl.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(procctrl.GetObjectState(), sdv::EObjectState::initialized); + + // Execute the server process + sdv::process::TProcessID tServerID = procctrl.Execute( + (GetExecDirectory() / "AppConnect_Server").generic_u8string(), {"204"}, sdv::process::EProcessRights::default_rights); + EXPECT_NE(tServerID, 0u); + + // Wait for 1 second + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + // Test for the running server + EXPECT_FALSE(procctrl.WaitForTerminate(tServerID, 1)); + + // Start the second system using the default instance. This simulates a client + sdv::app::CAppControl control; + EXPECT_FALSE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); + EXPECT_EQ(control.GetInstanceID(), 0u); + + // When startup succeeds an automatic connection to the server 204 should have been made. + bool bResult = control.Startup(R"toml([Application] +Mode = "Main" +Instance = 206 +[Console] +RedirectMon = true +)toml"); + EXPECT_TRUE(bResult); + ExpectConfiguredProvider(206u, "MySpecialConnection", false, "unix_domain_sockets_tunnel"); + + // Test utility - not allowed + SStages sStages{}; + std::string ss; + auto ptrUtilityHelloObject = sdv::core::CreateUtility("TestObject_HelloUtility"); + EXPECT_FALSE(ptrUtilityHelloObject); + + // Test system object - not allowed BUT CURRENTLY WORKING + sStages = {}; + ss.clear(); + auto pHelloObject = sdv::core::GetObject("TestObject_SystemHelloService"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) + ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_FALSE(sStages.bDevice); + EXPECT_FALSE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_TRUE(sStages.bSystemService); + EXPECT_FALSE(sStages.bUtility); + std::cout << "System service reports:" << std::endl << ss; + + //// Test device object - not allowed BUT CURRENTLY WORKING + //sStages = {}; + //ss.clear(); + //pHelloObject = sdv::core::GetObject("TestObject_HelloDevice"); + //EXPECT_TRUE(pHelloObject); + //if (pHelloObject) + // ss = pHelloObject->SayHello(sStages); + //EXPECT_FALSE(ss.empty()); + //EXPECT_TRUE(sStages.bDevice); + //EXPECT_FALSE(sStages.bBasicService); + //EXPECT_FALSE(sStages.bComplexService); + //EXPECT_TRUE(sStages.bSystemService); + //EXPECT_TRUE(sStages.bUtility); + //std::cout << "Device reports:" << std::endl << ss; + + // Test basic service object - tries to executes SayHello on Device, Utility and System Service, but not allowed + sStages = {}; + ss.clear(); + pHelloObject = sdv::core::GetObject("TestObject_BasicHelloService"); + EXPECT_TRUE(pHelloObject); + if (pHelloObject) + ss = pHelloObject->SayHello(sStages); + EXPECT_FALSE(ss.empty()); + EXPECT_FALSE(sStages.bDevice); + EXPECT_TRUE(sStages.bBasicService); + EXPECT_FALSE(sStages.bComplexService); + EXPECT_FALSE(sStages.bSystemService); + EXPECT_FALSE(sStages.bUtility); + std::cout << "Basic service reports:" << std::endl << ss; + + //// Test complex service object - executes SayHello on Complex Service, Device, Utility and System Service + // sStages = {}; + // ss.clear(); + // pHelloObject = sdv::core::GetObject("TestObject_ComplexHelloService"); + // EXPECT_TRUE(pHelloObject); + // if (pHelloObject) ss = pHelloObject->SayHello(sStages); + // EXPECT_FALSE(ss.empty()); + // EXPECT_TRUE(sStages.bDevice); + // EXPECT_TRUE(sStages.bBasicService); + // EXPECT_TRUE(sStages.bComplexService); + // EXPECT_TRUE(sStages.bSystemService); + // EXPECT_TRUE(sStages.bUtility); + // std::cout << "Basic service reports:" << std::endl << ss; + + control.Shutdown(); + EXPECT_FALSE(control.IsRunning()); + EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); + EXPECT_EQ(control.GetInstanceID(), 0u); + + // Release the mutex; this should shutdown the server + lock.unlock(); + + // Wait for 1 second + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + // Test for the running server - should be terminated + EXPECT_TRUE(procctrl.WaitForTerminate(tServerID, 10000)); + + // Just in case termination + procctrl.Terminate(tServerID); + + // End process control + procctrl.Shutdown(); +} diff --git a/tests/component_tests/app_connect/main.cpp b/tests/component_tests/app_connect/main.cpp new file mode 100644 index 0000000..64d8f7b --- /dev/null +++ b/tests/component_tests/app_connect/main.cpp @@ -0,0 +1,31 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#include +#include "../../../global/process_watchdog.h" +#include "../../../sdv_services/process_control/process_control.cpp" + +/** + * @brief Main function + */ +#if defined(_WIN32) && defined(_UNICODE) +extern "C" int wmain(int argc, wchar_t* argv[]) +#else +extern "C" int main(int argc, char* argv[]) +#endif +{ + CProcessWatchdog watchdog; + + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/component_tests/app_connect/main_server.cpp b/tests/component_tests/app_connect/main_server.cpp new file mode 100644 index 0000000..23f581e --- /dev/null +++ b/tests/component_tests/app_connect/main_server.cpp @@ -0,0 +1,99 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#include "../../../global/process_watchdog.h" +#include "../../../global/ipc_named_mutex.h" +#include +#include +#include +#include + +/** + * @brief Main function + */ +#if defined(_WIN32) && defined(_UNICODE) +extern "C" int wmain(int argc, wchar_t* argv[]) +#else +extern "C" int main(int argc, char* argv[]) +#endif +{ + CProcessWatchdog watchdog; + + // Workaround for GCC to make certain that POSIX thread library is loaded before the components are loaded. + // REASON: If the first call to a thread is done in a dynamic library, the application is already classified as single + // threaded and a termination is initiated. + // See: https://stackoverflow.com/questions/51209268/using-stdthread-in-a-library-loaded-with-dlopen-leads-to-a-sigsev + // NOTE EVE 27.05.2025: in release builds, starting and ending the thread right after each other causes incorrect behavior and + // leads in some cases to create a deadlock in the join-function. The solution is to add delays in the thread processing. + std::atomic_bool bThreadStarted = false; + std::thread thread = std::thread( + [&]() + { + std::this_thread::sleep_for(std::chrono::seconds(1)); + bThreadStarted = true; + }); + while (!bThreadStarted) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + if (thread.joinable()) + thread.join(); + + std::cout << "RuntimeDirectory = " << sdv::app::CAppControl::GetFrameworkRuntimeDirectory().generic_u8string() << std::endl; + std::cout << "InstallDirectory = " << sdv::app::CAppControl::GetComponentInstallDirectory().generic_u8string() << std::endl; + + std::cout << "Connect test application" << std::endl; + std::cout << "Copyright (C): 2026 ZF Friedrichshafen AG" << std::endl; + std::cout << "Author: Erik Verhoeven" << std::endl << std::endl; + + // Get the instance number from the command line. + if (argc != 2) + { + std::cout << "Missing argument; usage: AppConnect_Client " << std::endl; + return -1; + } +#if defined(_WIN32) && defined(_UNICODE) + size_t nInstance = wcstoul(argv[1], nullptr, 10); +#else + size_t nInstance = atoi(argv[1]); +#endif + std::cout << "Listening for connections on instance #" << nInstance << std::endl; + + sdv::app::CAppControl control; + bool bResult = control.Startup(R"toml([Application] +Mode = "Main" +Instance = )toml" + std::to_string(nInstance)); + if (!bResult) + { + std::cout << "Failed to start system..." << std::endl; + return -2; + } + + // Lifetime of the server is managed by the locking of a named mutex. As long as locking doesn't work, the server should run. + // Test every 250ms + ipc::named_mutex mtx("app_connect_test_mutex"); + bool bRunning = true; + while (bRunning) + { + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + if (mtx.try_lock()) + { + bRunning = false; + mtx.unlock(); + } + } + + control.Shutdown(); + std::cout << "Connect test application terminated... (instance #" << nInstance << ")" << std::endl; + return 0; +} diff --git a/tests/component_tests/app_connect/test_component.idl b/tests/component_tests/app_connect/test_component.idl new file mode 100644 index 0000000..4e0f46f --- /dev/null +++ b/tests/component_tests/app_connect/test_component.idl @@ -0,0 +1,46 @@ + /******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#include +#include + + /** + * @brief Stages the call might go through. + */ + struct SStages +{ + boolean bDevice; + boolean bBasicService; + boolean bComplexService; + boolean bSystemService; + boolean bUtility; +}; + +/** + * @brief Hello interface + */ +interface IHello +{ + /** + * @brief Say hello + * @param[in, out] sStages The struct stages will be filled by the components. The complex service calls the basic service. The + * basic service calls the device. The device calls the system service and the utility. + * @return The greetings string. + */ + u8string SayHello(inout SStages sStages) const; + + /** + * @brief Get the PID of the process the component is running in... + */ + sdv::process::TProcessID GetPID() const; +}; diff --git a/tests/component_tests/app_connect/test_component_basic_service.cpp b/tests/component_tests/app_connect/test_component_basic_service.cpp new file mode 100644 index 0000000..bdf2e68 --- /dev/null +++ b/tests/component_tests/app_connect/test_component_basic_service.cpp @@ -0,0 +1,72 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include "generated/test_component.h" + +inline sdv::process::TProcessID GetProcessID() +{ + static sdv::process::TProcessID tProcessID = 0; + if (!tProcessID) + { + sdv::process::IProcessInfo* pProcessInfo = sdv::core::GetObject("ProcessControlService"); + if (!pProcessInfo) return 0; + tProcessID = pProcessInfo->GetProcessID(); + } + return tProcessID; +} + +/** + * @brief Basic hello service + */ +class CBasicHelloService : public sdv::CSdvObject, public IHello +{ +public: + + BEGIN_SDV_INTERFACE_MAP() + SDV_INTERFACE_ENTRY(IHello) + END_SDV_INTERFACE_MAP() + + DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::basic_service) + DECLARE_OBJECT_CLASS_NAME("TestObject_BasicHelloService") + + /** + * @brief Say hello. Overload of IHello::SayHello. + * @param[in, out] sStages The struct stages will be filled by the components. The complex service calls the basic service. The + * basic service calls the device. The device calls the system service and the utility. + * @return The greetings string. + */ + virtual sdv::u8string SayHello(/*inout*/ SStages& sStages) const override + { + sStages.bBasicService = true; + + // Request for the device + auto pHelloObject = sdv::core::GetObject("TestObject_HelloDevice"); + return "PID#" + std::to_string(GetPID()) + " Hello from basic service\n" + + std::string(pHelloObject ? pHelloObject->SayHello(sStages) : ""); + } + + /** + * @brief Get the PID of the process the component is running in... Overload of IHello::GetPID. + */ + sdv::process::TProcessID GetPID() const + { + return GetProcessID(); + } +}; +DEFINE_SDV_OBJECT(CBasicHelloService) diff --git a/tests/component_tests/app_connect/test_component_complex_service.cpp b/tests/component_tests/app_connect/test_component_complex_service.cpp new file mode 100644 index 0000000..dc9c793 --- /dev/null +++ b/tests/component_tests/app_connect/test_component_complex_service.cpp @@ -0,0 +1,81 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include "generated/test_component.h" + +inline sdv::process::TProcessID GetProcessID() +{ + static sdv::process::TProcessID tProcessID = 0; + if (!tProcessID) + { + sdv::process::IProcessInfo* pProcessInfo = sdv::core::GetObject("ProcessControlService"); + if (!pProcessInfo) return 0; + tProcessID = pProcessInfo->GetProcessID(); + } + return tProcessID; +} + +/** + * @brief Complex hello service + */ +class CComplexHelloService : public sdv::CSdvObject, public IHello +{ +public: + CComplexHelloService() + { + std::cout << "CComplexHelloService constructor" << std::endl; + } + ~CComplexHelloService() + { + std::cout << "CComplexHelloService destructor" << std::endl; + } + + BEGIN_SDV_INTERFACE_MAP() + SDV_INTERFACE_ENTRY(IHello) + END_SDV_INTERFACE_MAP() + + DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::complex_service) + DECLARE_OBJECT_CLASS_NAME("TestObject_ComplexHelloService") + + /** + * @brief Say hello. Overload of IHello::SayHello. + * @param[in, out] sStages The struct stages will be filled by the components. The complex service calls the basic service. The + * basic service calls the device. The device calls the system service and the utility. + * @return The greetings string. + */ + virtual sdv::u8string SayHello(/*inout*/ SStages& sStages) const override + { + sStages.bComplexService = true; + + // Request for the basic server + auto pHelloObject = sdv::core::GetObject("TestObject_BasicHelloService"); + return "PID#" + std::to_string(GetPID()) + " Hello from complex service\n" + + std::string(pHelloObject ? pHelloObject->SayHello(sStages) : ""); + } + + /** + * @brief Get the PID of the process the component is running in... Overload of IHello::GetPID. + */ + sdv::process::TProcessID GetPID() const + { + return GetProcessID(); + } +}; +DEFINE_SDV_OBJECT(CComplexHelloService) + diff --git a/tests/component_tests/app_connect/test_component_device.cpp b/tests/component_tests/app_connect/test_component_device.cpp new file mode 100644 index 0000000..1d8c8c6 --- /dev/null +++ b/tests/component_tests/app_connect/test_component_device.cpp @@ -0,0 +1,76 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include "generated/test_component.h" + +inline sdv::process::TProcessID GetProcessID() +{ + static sdv::process::TProcessID tProcessID = 0; + if (!tProcessID) + { + sdv::process::IProcessInfo* pProcessInfo = sdv::core::GetObject("ProcessControlService"); + if (!pProcessInfo) return 0; + tProcessID = pProcessInfo->GetProcessID(); + } + return tProcessID; +} + +/** + * @brief Hello device + */ +class CHelloDevice : public sdv::CSdvObject, public IHello +{ +public: + + BEGIN_SDV_INTERFACE_MAP() + SDV_INTERFACE_ENTRY(IHello) + END_SDV_INTERFACE_MAP() + + DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::device) + DECLARE_OBJECT_CLASS_NAME("TestObject_HelloDevice") + + /** + * @brief Say hello. Overload of IHello::SayHello. + * @param[in, out] sStages The struct stages will be filled by the components. The complex service calls the basic service. The + * basic service calls the device. The device calls the system service and the utility. + * @return The greetings string. + */ + virtual sdv::u8string SayHello(/*inout*/ SStages& sStages) const override + { + sStages.bDevice = true; + + // Request for the system service + auto pSystemHelloObject = sdv::core::GetObject("TestObject_SystemHelloService"); + auto ptrUtilityHelloObject = sdv::core::CreateUtility("TestObject_HelloUtility"); + auto pUtilityHelloObject = ptrUtilityHelloObject.GetInterface(); + return "PID#" + std::to_string(GetPID()) + " Hello from device\n" + + std::string(pSystemHelloObject ? pSystemHelloObject->SayHello(sStages) : "") + + std::string(pUtilityHelloObject ? pUtilityHelloObject->SayHello(sStages) : ""); + } + + /** + * @brief Get the PID of the process the component is running in... Overload of IHello::GetPID. + */ + sdv::process::TProcessID GetPID() const + { + return GetProcessID(); + } +}; +DEFINE_SDV_OBJECT(CHelloDevice) + diff --git a/tests/unit_tests/app_connect/app_connect_local.cpp b/tests/component_tests/app_connect/test_component_ps.cpp similarity index 69% rename from tests/unit_tests/app_connect/app_connect_local.cpp rename to tests/component_tests/app_connect/test_component_ps.cpp index 2b6c398..11b64ac 100644 --- a/tests/unit_tests/app_connect/app_connect_local.cpp +++ b/tests/component_tests/app_connect/test_component_ps.cpp @@ -11,10 +11,4 @@ * Erik Verhoeven - initial API and implementation ********************************************************************************/ -/** -- Default instantiate listener -- Instantiate with specific info -- Multiple instantiate with identical info (failure) -- Multiple instantiate with different info -- Default instantiate client and get connection information -- */ \ No newline at end of file +#include "generated/ps/proxystub.cpp" \ No newline at end of file diff --git a/tests/component_tests/app_connect/test_component_system_service.cpp b/tests/component_tests/app_connect/test_component_system_service.cpp new file mode 100644 index 0000000..37c6250 --- /dev/null +++ b/tests/component_tests/app_connect/test_component_system_service.cpp @@ -0,0 +1,69 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include "generated/test_component.h" + +inline sdv::process::TProcessID GetProcessID() +{ + static sdv::process::TProcessID tProcessID = 0; + if (!tProcessID) + { + sdv::process::IProcessInfo* pProcessInfo = sdv::core::GetObject("ProcessControlService"); + if (!pProcessInfo) return 0; + tProcessID = pProcessInfo->GetProcessID(); + } + return tProcessID; +} + +/** + * @brief Hello utlity + */ +class CSystemHelloService : public sdv::CSdvObject, public IHello +{ +public: + + BEGIN_SDV_INTERFACE_MAP() + SDV_INTERFACE_ENTRY(IHello) + END_SDV_INTERFACE_MAP() + + DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::system_object) + DECLARE_OBJECT_CLASS_NAME("TestObject_SystemHelloService") + + /** + * @brief Say hello. Overload of IHello::SayHello. + * @param[in, out] sStages The struct stages will be filled by the components. The complex service calls the basic service. The + * basic service calls the device. The device calls the system service and the utility. + * @return The greetings string. + */ + virtual sdv::u8string SayHello(/*inout*/ SStages& sStages) const override + { + sStages.bSystemService = true; + + return "PID#" + std::to_string(GetPID()) + " Hello from system service\n"; + } + + /** + * @brief Get the PID of the process the component is running in... Overload of IHello::GetPID. + */ + sdv::process::TProcessID GetPID() const + { + return GetProcessID(); + } +}; +DEFINE_SDV_OBJECT(CSystemHelloService) diff --git a/tests/component_tests/app_connect/test_component_utility.cpp b/tests/component_tests/app_connect/test_component_utility.cpp new file mode 100644 index 0000000..2414ea3 --- /dev/null +++ b/tests/component_tests/app_connect/test_component_utility.cpp @@ -0,0 +1,70 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include "generated/test_component.h" + +inline sdv::process::TProcessID GetProcessID() +{ + static sdv::process::TProcessID tProcessID = 0; + if (!tProcessID) + { + sdv::process::IProcessInfo* pProcessInfo = sdv::core::GetObject("ProcessControlService"); + if (!pProcessInfo) return 0; + tProcessID = pProcessInfo->GetProcessID(); + } + return tProcessID; +} + +/** + * @brief Hello utlity + */ +class CHelloUtility : public sdv::CSdvObject, public IHello +{ +public: + + BEGIN_SDV_INTERFACE_MAP() + SDV_INTERFACE_ENTRY(IHello) + END_SDV_INTERFACE_MAP() + + DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::utility) + DECLARE_OBJECT_CLASS_NAME("TestObject_HelloUtility") + + /** + * @brief Say hello. Overload of IHello::SayHello. + * @param[in, out] sStages The struct stages will be filled by the components. The complex service calls the basic service. The + * basic service calls the device. The device calls the system service and the utility. + * @return The greetings string. + */ + virtual sdv::u8string SayHello(/*inout*/ SStages& sStages) const override + { + sStages.bUtility = true; + + return "PID#" + std::to_string(GetPID()) + " Hello from basic service\n"; + } + + /** + * @brief Get the PID of the process the component is running in... Overload of IHello::GetPID. + */ + sdv::process::TProcessID GetPID() const + { + return GetProcessID(); + } +}; +DEFINE_SDV_OBJECT(CHelloUtility) + diff --git a/tests/component_tests/app_control/CMakeLists.txt b/tests/component_tests/app_control/CMakeLists.txt index 2467551..15f935e 100644 --- a/tests/component_tests/app_control/CMakeLists.txt +++ b/tests/component_tests/app_control/CMakeLists.txt @@ -25,9 +25,16 @@ add_executable(ComponentTest_AppControl target_link_libraries(ComponentTest_AppControl ${CMAKE_DL_LIBS} GTest::GTest) -# Add the Data Dispatch Service unittest +# Add the AppControl component test add_test(NAME ComponentTest_AppControl COMMAND ComponentTest_AppControl WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) +# Execute the installation helper utility to create an installation manifest for the core files. +add_custom_target(ComponentTest_AppControl_install_manifest + COMMAND "$" DIRECT_INSTALL ComponentTest_Repository --instance2009 "$" "$" "-I$" --overwrite --user_config+TestObject_ComplexHelloService+TestObject_HelloUtility --interface_config+Example_Object+Example_Object_2+TestObject_HelloDevice+TestObject_BasicHelloService+TestObject_SystemHelloService + DEPENDS ComponentTest_Repository_test_module ComponentTest_Repository_ps + WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} +) + # Execute the test # Currently disabled due to issues with MINGW causing the application to sporadically crash. A bug ticket has been filed: # https://dev.azure.com/SW4ZF/AZP-074_DivDI_SofDCarResearch/_workitems/edit/608132 @@ -39,3 +46,4 @@ add_custom_command(TARGET ComponentTest_AppControl POST_BUILD # Build dependencies add_dependencies(ComponentTest_AppControl dependency_sdv_components) add_dependencies(ComponentTest_AppControl ComponentTest_Repository) # Use the repository components for this test +add_dependencies(ComponentTest_AppControl ComponentTest_AppControl_install_manifest) # Use the repository components for this test diff --git a/tests/component_tests/app_control/app_control_test_event_handler.cpp b/tests/component_tests/app_control/app_control_test_event_handler.cpp index ee571e8..3f5c61e 100644 --- a/tests/component_tests/app_control/app_control_test_event_handler.cpp +++ b/tests/component_tests/app_control/app_control_test_event_handler.cpp @@ -254,7 +254,9 @@ TEST(CoreLibrary_AppControl_EventHandler, Startup_Standalone_NoConfig) } } -TEST(CoreLibrary_AppControl_EventHandler, Startup_External_NoConfig) +// Currently disabled due to absence of server accepting connections. +// https://dev.azure.com/SW4ZF/AZP-431_DivDI_Vehicle_API/_workitems/edit/800643 +TEST(CoreLibrary_AppControl_EventHandler, DISABLED_Startup_External_NoConfig) { CAppEventHandler handler; sdv::app::IAppControl* pControl = sdv::core::GetCore(); @@ -331,13 +333,13 @@ TEST(CoreLibrary_AppControl_EventHandler, Startup_Isolated_NoConfig) EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::no_context); EXPECT_EQ(pContext->GetInstanceID(), 0u); - bool bResult = pControl->Startup(R"code( + bool bResult = pControl->Startup(R"toml( [Application] Mode = "Isolated" [Console] Report = "Silent" -)code", &handler); +)toml", &handler); EXPECT_TRUE(bResult); EXPECT_EQ(pOperation->GetOperationState(), sdv::app::EAppOperationState::running); EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::isolated); @@ -589,11 +591,11 @@ TEST(CoreLibrary_AppControl_EventHandler, Startup_Default_DefineInstance) EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::no_context); EXPECT_EQ(pContext->GetInstanceID(), 0u); - bool bResult = pControl->Startup("[Application]\nInstance=2005", &handler); + bool bResult = pControl->Startup("[Application]\nInstance=2009", &handler); EXPECT_TRUE(bResult); EXPECT_EQ(pOperation->GetOperationState(), sdv::app::EAppOperationState::running); EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::standalone); - EXPECT_EQ(pContext->GetInstanceID(), 2005u); + EXPECT_EQ(pContext->GetInstanceID(), 2009u); sdv::app::EAppOperationState rgeStartupStates[] = { sdv::app::EAppOperationState::initializing, @@ -652,11 +654,11 @@ TEST(CoreLibrary_AppControl_EventHandler, Startup_Standalone_DefineInstance) EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::no_context); EXPECT_EQ(pContext->GetInstanceID(), 0u); - bool bResult = pControl->Startup("[Application]\nMode=\"Standalone\"\nInstance=2005", &handler); + bool bResult = pControl->Startup("[Application]\nMode=\"Standalone\"\nInstance=2009", &handler); EXPECT_TRUE(bResult); EXPECT_EQ(pOperation->GetOperationState(), sdv::app::EAppOperationState::running); EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::standalone); - EXPECT_EQ(pContext->GetInstanceID(), 2005u); + EXPECT_EQ(pContext->GetInstanceID(), 2009u); sdv::app::EAppOperationState rgeStartupStates[] = { sdv::app::EAppOperationState::initializing, @@ -701,7 +703,9 @@ TEST(CoreLibrary_AppControl_EventHandler, Startup_Standalone_DefineInstance) } } -TEST(CoreLibrary_AppControl_EventHandler, Startup_External_DefineInstance) +// Currently disabled due to absence of server accepting connections. +// https://dev.azure.com/SW4ZF/AZP-431_DivDI_Vehicle_API/_workitems/edit/800643 +TEST(CoreLibrary_AppControl_EventHandler, DISABLED_Startup_External_DefineInstance) { CAppEventHandler handler; sdv::app::IAppControl* pControl = sdv::core::GetCore(); @@ -715,11 +719,11 @@ TEST(CoreLibrary_AppControl_EventHandler, Startup_External_DefineInstance) EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::no_context); EXPECT_EQ(pContext->GetInstanceID(), 0u); - bool bResult = pControl->Startup("[Application]\nMode=\"External\"\nInstance=2005", &handler); + bool bResult = pControl->Startup("[Application]\nMode=\"External\"\nInstance=2009", &handler); EXPECT_TRUE(bResult); EXPECT_EQ(pOperation->GetOperationState(), sdv::app::EAppOperationState::running); EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::external); - EXPECT_EQ(pContext->GetInstanceID(), 2005u); + EXPECT_EQ(pContext->GetInstanceID(), 2009u); sdv::app::EAppOperationState rgeStartupStates[] = { sdv::app::EAppOperationState::initializing, @@ -778,16 +782,16 @@ TEST(CoreLibrary_AppControl_EventHandler, Startup_Isolated_DefineInstance) EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::no_context); EXPECT_EQ(pContext->GetInstanceID(), 0u); - std::string ssConfig = R"code([Application] + std::string ssConfig = R"toml([Application] Mode = "Isolated" -Instance = 2005 -Connection = ")code"; +Instance = 2009 +Connection = ")toml"; ssConfig += Base64EncodePlainText("test") + "\""; bool bResult = pControl->Startup(ssConfig, &handler); EXPECT_TRUE(bResult); EXPECT_EQ(pOperation->GetOperationState(), sdv::app::EAppOperationState::running); EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::isolated); - EXPECT_EQ(pContext->GetInstanceID(), 2005u); + EXPECT_EQ(pContext->GetInstanceID(), 2009u); sdv::app::EAppOperationState rgeStartupStates[] = { sdv::app::EAppOperationState::initializing, @@ -846,11 +850,11 @@ TEST(CoreLibrary_AppControl_EventHandler, Startup_Main_DefineInstance) EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::no_context); EXPECT_EQ(pContext->GetInstanceID(), 0u); - bool bResult = pControl->Startup("[Application]\nMode=\"Main\"\nInstance=2005", &handler); + bool bResult = pControl->Startup("[Application]\nMode=\"Main\"\nInstance=2009", &handler); EXPECT_TRUE(bResult); EXPECT_EQ(pOperation->GetOperationState(), sdv::app::EAppOperationState::running); EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::main); - EXPECT_EQ(pContext->GetInstanceID(), 2005u); + EXPECT_EQ(pContext->GetInstanceID(), 2009u); sdv::app::EAppOperationState rgeStartupStates[] = { sdv::app::EAppOperationState::initializing, @@ -909,11 +913,11 @@ TEST(CoreLibrary_AppControl_EventHandler, Startup_Essential_DefineInstance) EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::no_context); EXPECT_EQ(pContext->GetInstanceID(), 0u); - bool bResult = pControl->Startup("[Application]\nMode=\"Essential\"\nInstance=2005", &handler); + bool bResult = pControl->Startup("[Application]\nMode=\"Essential\"\nInstance=2009", &handler); EXPECT_TRUE(bResult); EXPECT_EQ(pOperation->GetOperationState(), sdv::app::EAppOperationState::running); EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::essential); - EXPECT_EQ(pContext->GetInstanceID(), 2005u); + EXPECT_EQ(pContext->GetInstanceID(), 2009u); sdv::app::EAppOperationState rgeStartupStates[] = { sdv::app::EAppOperationState::initializing, @@ -972,11 +976,11 @@ TEST(CoreLibrary_AppControl_EventHandler, Startup_Maintenance_DefineInstance) EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::no_context); EXPECT_EQ(pContext->GetInstanceID(), 0u); - bool bResult = pControl->Startup("[Application]\nMode=\"Maintenance\"\nInstance=2005", &handler); + bool bResult = pControl->Startup("[Application]\nMode=\"Maintenance\"\nInstance=2009", &handler); EXPECT_TRUE(bResult); EXPECT_EQ(pOperation->GetOperationState(), sdv::app::EAppOperationState::running); EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::maintenance); - EXPECT_EQ(pContext->GetInstanceID(), 2005u); + EXPECT_EQ(pContext->GetInstanceID(), 2009u); sdv::app::EAppOperationState rgeStartupStates[] = { sdv::app::EAppOperationState::initializing, @@ -1042,8 +1046,10 @@ TEST(CoreLibrary_AppControl_EventHandler, RunLoop_Default) EXPECT_EQ(pContext->GetInstanceID(), 2007u); auto tpStart = std::chrono::high_resolution_clock::now(); - std::thread threadShutdownRequest([]() + sdv::core::secure_thread threadShutdownRequest([&]() { + while (pOperation->GetOperationState() != sdv::app::EAppOperationState::running) + std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(250)); RequestShutdown(2007u); }); @@ -1080,8 +1086,10 @@ TEST(CoreLibrary_AppControl_EventHandler, RunLoop_Standalone) EXPECT_EQ(pContext->GetInstanceID(), 2007u); auto tpStart = std::chrono::high_resolution_clock::now(); - std::thread threadShutdownRequest([]() + sdv::core::secure_thread threadShutdownRequest([&]() { + while (pOperation->GetOperationState() != sdv::app::EAppOperationState::running) + std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(250)); RequestShutdown(2007u); }); @@ -1097,7 +1105,9 @@ TEST(CoreLibrary_AppControl_EventHandler, RunLoop_Standalone) EXPECT_EQ(pContext->GetInstanceID(), 0u); } -TEST(CoreLibrary_AppControl_EventHandler, RunLoop_External) +// Currently disabled due to absence of server accepting connections. +// https://dev.azure.com/SW4ZF/AZP-431_DivDI_Vehicle_API/_workitems/edit/800643 +TEST(CoreLibrary_AppControl_EventHandler, DISABLED_RunLoop_External) { CAppEventHandler handler; sdv::app::IAppControl* pControl = sdv::core::GetCore(); @@ -1118,8 +1128,10 @@ TEST(CoreLibrary_AppControl_EventHandler, RunLoop_External) EXPECT_EQ(pContext->GetInstanceID(), 2007u); auto tpStart = std::chrono::high_resolution_clock::now(); - std::thread threadShutdownRequest([]() + sdv::core::secure_thread threadShutdownRequest([&]() { + while (pOperation->GetOperationState() != sdv::app::EAppOperationState::running) + std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(250)); RequestShutdown(2007u); }); @@ -1149,10 +1161,10 @@ TEST(CoreLibrary_AppControl_EventHandler, RunLoop_Isolated) EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::no_context); EXPECT_EQ(pContext->GetInstanceID(), 0u); - std::string ssConfig = R"code([Application] + std::string ssConfig = R"toml([Application] Mode = "Isolated" Instance = 2007 -Connection = ")code"; +Connection = ")toml"; ssConfig += Base64EncodePlainText("test") + "\""; bool bResult = pControl->Startup(ssConfig, &handler); EXPECT_TRUE(bResult); @@ -1161,8 +1173,10 @@ Connection = ")code"; EXPECT_EQ(pContext->GetInstanceID(), 2007u); auto tpStart = std::chrono::high_resolution_clock::now(); - std::thread threadShutdownRequest([]() + sdv::core::secure_thread threadShutdownRequest([&]() { + while (pOperation->GetOperationState() != sdv::app::EAppOperationState::running) + std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(250)); sdv::app::IAppShutdownRequest* pShutdownRequest = sdv::core::GetObject("AppControlService"); ASSERT_NE(pShutdownRequest, nullptr); @@ -1201,8 +1215,10 @@ TEST(CoreLibrary_AppControl_EventHandler, RunLoop_Main) EXPECT_EQ(pContext->GetInstanceID(), 2007u); auto tpStart = std::chrono::high_resolution_clock::now(); - std::thread threadShutdownRequest([]() + sdv::core::secure_thread threadShutdownRequest([&]() { + while (pOperation->GetOperationState() != sdv::app::EAppOperationState::running) + std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(250)); sdv::app::IAppShutdownRequest* pShutdownRequest = sdv::core::GetObject("AppControlService"); ASSERT_NE(pShutdownRequest, nullptr); @@ -1241,8 +1257,10 @@ TEST(CoreLibrary_AppControl_EventHandler, RunLoop_Essential) EXPECT_EQ(pContext->GetInstanceID(), 2007u); auto tpStart = std::chrono::high_resolution_clock::now(); - std::thread threadShutdownRequest([]() + sdv::core::secure_thread threadShutdownRequest([&]() { + while (pOperation->GetOperationState() != sdv::app::EAppOperationState::running) + std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(250)); RequestShutdown(2007u); }); diff --git a/tests/component_tests/app_control/app_control_test_mgnt_class.cpp b/tests/component_tests/app_control/app_control_test_mgnt_class.cpp index 32a9110..56895c5 100644 --- a/tests/component_tests/app_control/app_control_test_mgnt_class.cpp +++ b/tests/component_tests/app_control/app_control_test_mgnt_class.cpp @@ -77,8 +77,8 @@ TEST(AppControl, Startup_Standalone_NoConfig) EXPECT_EQ(control.GetInstanceID(), 0u); } -// Testing the external application requires a process running that was initialized as main with the same instance ID. This process -// needs to be started before and shutdown after the execution of this test. +// Currently disabled due to absence of server accepting connections. +// https://dev.azure.com/SW4ZF/AZP-431_DivDI_Vehicle_API/_workitems/edit/800643 TEST(AppControl, DISABLED_Startup_External_NoConfig) { sdv::app::CAppControl control; @@ -105,13 +105,13 @@ TEST(AppControl, Startup_Isolated_NoConfig) EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); EXPECT_EQ(control.GetInstanceID(), 0u); - bool bResult = control.Startup(R"code( + bool bResult = control.Startup(R"toml( [Application] Mode = "Isolated" [Console] Report = "Silent" -)code"); +)toml"); EXPECT_TRUE(bResult); EXPECT_TRUE(control.IsRunning()); EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::isolated); @@ -187,11 +187,11 @@ TEST(AppControl, Startup_Default_DefineInstance) EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); EXPECT_EQ(control.GetInstanceID(), 0u); - bool bResult = control.Startup("[Application]\nInstance=2005"); + bool bResult = control.Startup("[Application]\nInstance=2009"); EXPECT_TRUE(bResult); EXPECT_TRUE(control.IsRunning()); EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::standalone); - EXPECT_EQ(control.GetInstanceID(), 2005u); + EXPECT_EQ(control.GetInstanceID(), 2009u); control.Shutdown(); EXPECT_FALSE(control.IsRunning()); @@ -206,11 +206,11 @@ TEST(AppControl, Startup_Standalone_DefineInstance) EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); EXPECT_EQ(control.GetInstanceID(), 0u); - bool bResult = control.Startup("[Application]\nMode=\"Standalone\"\nInstance=2005"); + bool bResult = control.Startup("[Application]\nMode=\"Standalone\"\nInstance=2009"); EXPECT_TRUE(bResult); EXPECT_TRUE(control.IsRunning()); EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::standalone); - EXPECT_EQ(control.GetInstanceID(), 2005u); + EXPECT_EQ(control.GetInstanceID(), 2009u); control.Shutdown(); EXPECT_FALSE(control.IsRunning()); @@ -218,8 +218,8 @@ TEST(AppControl, Startup_Standalone_DefineInstance) EXPECT_EQ(control.GetInstanceID(), 0u); } -// Testing the external application requires a process running that was initialized as main with the same instance ID. This process -// needs to be started before and shutdown after the execution of this test. +// Currently disabled due to absence of server accepting connections. +// https://dev.azure.com/SW4ZF/AZP-431_DivDI_Vehicle_API/_workitems/edit/800643 TEST(AppControl, DISABLED_Startup_External_DefineInstance) { sdv::app::CAppControl control; @@ -227,11 +227,11 @@ TEST(AppControl, DISABLED_Startup_External_DefineInstance) EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); EXPECT_EQ(control.GetInstanceID(), 0u); - bool bResult = control.Startup("[Application]\nMode=\"External\"\nInstance=2005"); + bool bResult = control.Startup("[Application]\nMode=\"External\"\nInstance=2009"); EXPECT_TRUE(bResult); EXPECT_TRUE(control.IsRunning()); EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::external); - EXPECT_EQ(control.GetInstanceID(), 2005u); + EXPECT_EQ(control.GetInstanceID(), 2009u); control.Shutdown(); EXPECT_FALSE(control.IsRunning()); @@ -246,16 +246,16 @@ TEST(AppControl, Startup_Isolated_DefineInstance) EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); EXPECT_EQ(control.GetInstanceID(), 0u); - std::string ssConfig = R"code([Application] + std::string ssConfig = R"toml([Application] Mode = "Isolated" -Instance = 2005 -Connection = ")code"; +Instance = 2009 +Connection = ")toml"; ssConfig += Base64EncodePlainText("test") + "\""; bool bResult = control.Startup(ssConfig); EXPECT_TRUE(bResult); EXPECT_TRUE(control.IsRunning()); EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::isolated); - EXPECT_EQ(control.GetInstanceID(), 2005u); + EXPECT_EQ(control.GetInstanceID(), 2009u); control.Shutdown(); EXPECT_FALSE(control.IsRunning()); @@ -270,11 +270,11 @@ TEST(AppControl, Startup_Main_DefineInstance) EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); EXPECT_EQ(control.GetInstanceID(), 0u); - bool bResult = control.Startup("[Application]\nMode=\"Main\"\nInstance=2005"); + bool bResult = control.Startup("[Application]\nMode=\"Main\"\nInstance=2009"); EXPECT_TRUE(bResult); EXPECT_TRUE(control.IsRunning()); EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::main); - EXPECT_EQ(control.GetInstanceID(), 2005u); + EXPECT_EQ(control.GetInstanceID(), 2009u); control.Shutdown(); EXPECT_FALSE(control.IsRunning()); @@ -289,11 +289,11 @@ TEST(AppControl, Startup_Essential_DefineInstance) EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); EXPECT_EQ(control.GetInstanceID(), 0u); - bool bResult = control.Startup("[Application]\nMode=\"Essential\"\nInstance=2005"); + bool bResult = control.Startup("[Application]\nMode=\"Essential\"\nInstance=2009"); EXPECT_TRUE(bResult); EXPECT_TRUE(control.IsRunning()); EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::essential); - EXPECT_EQ(control.GetInstanceID(), 2005u); + EXPECT_EQ(control.GetInstanceID(), 2009u); control.Shutdown(); EXPECT_FALSE(control.IsRunning()); @@ -308,11 +308,11 @@ TEST(AppControl, Startup_Maintenance_DefineInstance) EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); EXPECT_EQ(control.GetInstanceID(), 0u); - bool bResult = control.Startup("[Application]\nMode=\"Maintenance\"\nInstance=2005"); + bool bResult = control.Startup("[Application]\nMode=\"Maintenance\"\nInstance=2009"); EXPECT_TRUE(bResult); EXPECT_TRUE(control.IsRunning()); EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::maintenance); - EXPECT_EQ(control.GetInstanceID(), 2005u); + EXPECT_EQ(control.GetInstanceID(), 2009u); control.Shutdown(); EXPECT_FALSE(control.IsRunning()); @@ -334,8 +334,10 @@ TEST(AppControl, RunLoop_Default) EXPECT_EQ(control.GetInstanceID(), 2007u); auto tpStart = std::chrono::high_resolution_clock::now(); - std::thread threadShutdownRequest([]() + sdv::core::secure_thread threadShutdownRequest([&]() { + while (!control.IsRunning()) + std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(250)); RequestShutdown(2007u); }); @@ -363,8 +365,10 @@ TEST(AppControl, RunLoop_Standalone) EXPECT_EQ(control.GetInstanceID(), 2007u); auto tpStart = std::chrono::high_resolution_clock::now(); - std::thread threadShutdownRequest([]() + sdv::core::secure_thread threadShutdownRequest([&]() { + while (!control.IsRunning()) + std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(250)); RequestShutdown(2007u); }); @@ -378,8 +382,8 @@ TEST(AppControl, RunLoop_Standalone) EXPECT_EQ(control.GetInstanceID(), 0u); } -// Testing the external application requires a process running that was initialized as main with the same instance ID. This process -// needs to be started before and shutdown after the execution of this test. +// Currently disabled due to absence of server accepting connections. +// https://dev.azure.com/SW4ZF/AZP-431_DivDI_Vehicle_API/_workitems/edit/800643 TEST(AppControl, DISABLED_RunLoop_External) { sdv::app::CAppControl control; @@ -394,8 +398,10 @@ TEST(AppControl, DISABLED_RunLoop_External) EXPECT_EQ(control.GetInstanceID(), 2007u); auto tpStart = std::chrono::high_resolution_clock::now(); - std::thread threadShutdownRequest([]() + sdv::core::secure_thread threadShutdownRequest([&]() { + while (!control.IsRunning()) + std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(250)); RequestShutdown(2007u); }); @@ -416,10 +422,10 @@ TEST(AppControl, RunLoop_Isolated) EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); EXPECT_EQ(control.GetInstanceID(), 0u); - std::string ssConfig = R"code([Application] + std::string ssConfig = R"toml([Application] Mode = "Isolated" Instance = 2007 -Connection = ")code"; +Connection = ")toml"; ssConfig += Base64EncodePlainText("test") + "\""; bool bResult = control.Startup(ssConfig); EXPECT_TRUE(bResult); @@ -428,8 +434,10 @@ Connection = ")code"; EXPECT_EQ(control.GetInstanceID(), 2007u); auto tpStart = std::chrono::high_resolution_clock::now(); - std::thread threadShutdownRequest([]() + sdv::core::secure_thread threadShutdownRequest([&]() { + while (!control.IsRunning()) + std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(250)); sdv::app::IAppShutdownRequest* pShutdownRequest = sdv::core::GetObject("AppControlService"); ASSERT_NE(pShutdownRequest, nullptr); @@ -459,8 +467,10 @@ TEST(AppControl, RunLoop_Main) EXPECT_EQ(control.GetInstanceID(), 2007u); auto tpStart = std::chrono::high_resolution_clock::now(); - std::thread threadShutdownRequest([]() + sdv::core::secure_thread threadShutdownRequest([&]() { + while (!control.IsRunning()) + std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(250)); sdv::app::IAppShutdownRequest* pShutdownRequest = sdv::core::GetObject("AppControlService"); ASSERT_NE(pShutdownRequest, nullptr); @@ -490,8 +500,10 @@ TEST(AppControl, RunLoop_Essential) EXPECT_EQ(control.GetInstanceID(), 2007u); auto tpStart = std::chrono::high_resolution_clock::now(); - std::thread threadShutdownRequest([]() + sdv::core::secure_thread threadShutdownRequest([&]() { + while (!control.IsRunning()) + std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(250)); RequestShutdown(2007u); }); @@ -526,4 +538,3 @@ TEST(AppControl, RunLoop_Maintenance) EXPECT_EQ(control.GetAppContext(), sdv::app::EAppContext::no_context); EXPECT_EQ(control.GetInstanceID(), 0u); } - diff --git a/tests/component_tests/app_control/app_control_test_no_event_handler.cpp b/tests/component_tests/app_control/app_control_test_no_event_handler.cpp index 01a87a4..677ce6d 100644 --- a/tests/component_tests/app_control/app_control_test_no_event_handler.cpp +++ b/tests/component_tests/app_control/app_control_test_no_event_handler.cpp @@ -51,7 +51,7 @@ TEST(CoreLibrary_AppControl_NoEventHandler, Startup_Default_NoConfig) sdv::app::IAppControl* pControl = sdv::core::GetCore(); ASSERT_NE(pControl, nullptr); sdv::app::IAppContext* pContext = sdv::core::GetCore(); - ASSERT_NE(pContext, nullptr); + ASSERT_NE(pContext, nullptr); sdv::app::IAppOperation* pOperation = sdv::core::GetCore(); ASSERT_NE(pOperation, nullptr); @@ -96,7 +96,9 @@ TEST(CoreLibrary_AppControl_NoEventHandler, Startup_Standalone_NoConfig) EXPECT_EQ(pContext->GetInstanceID(), 0u); } -TEST(CoreLibrary_AppControl_NoEventHandler, Startup_External_NoConfig) +// Currently disabled due to absence of server accepting connections. +// https://dev.azure.com/SW4ZF/AZP-431_DivDI_Vehicle_API/_workitems/edit/800643 +TEST(CoreLibrary_AppControl_NoEventHandler, DISABLED_Startup_External_NoConfig) { sdv::app::IAppControl* pControl = sdv::core::GetCore(); ASSERT_NE(pControl, nullptr); @@ -134,13 +136,13 @@ TEST(CoreLibrary_AppControl_NoEventHandler, Startup_Isolated_NoConfig) EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::no_context); EXPECT_EQ(pContext->GetInstanceID(), 0u); - bool bResult = pControl->Startup(R"code( + bool bResult = pControl->Startup(R"toml( [Application] Mode = "Isolated" [Console] Report = "Silent" -)code", nullptr); +)toml", nullptr); EXPECT_TRUE(bResult); EXPECT_EQ(pOperation->GetOperationState(), sdv::app::EAppOperationState::running); EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::isolated); @@ -240,11 +242,11 @@ TEST(CoreLibrary_AppControl_NoEventHandler, Startup_Default_DefineInstance) EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::no_context); EXPECT_EQ(pContext->GetInstanceID(), 0u); - bool bResult = pControl->Startup("[Application]\nInstance=2005", nullptr); + bool bResult = pControl->Startup("[Application]\nInstance=2009", nullptr); EXPECT_TRUE(bResult); EXPECT_EQ(pOperation->GetOperationState(), sdv::app::EAppOperationState::running); EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::standalone); - EXPECT_EQ(pContext->GetInstanceID(), 2005u); + EXPECT_EQ(pContext->GetInstanceID(), 2009u); pControl->Shutdown(true); EXPECT_EQ(pOperation->GetOperationState(), sdv::app::EAppOperationState::not_started); @@ -265,11 +267,11 @@ TEST(CoreLibrary_AppControl_NoEventHandler, Startup_Standalone_DefineInstance) EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::no_context); EXPECT_EQ(pContext->GetInstanceID(), 0u); - bool bResult = pControl->Startup("[Application]\nMode=\"Standalone\"\nInstance=2005", nullptr); + bool bResult = pControl->Startup("[Application]\nMode=\"Standalone\"\nInstance=2009", nullptr); EXPECT_TRUE(bResult); EXPECT_EQ(pOperation->GetOperationState(), sdv::app::EAppOperationState::running); EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::standalone); - EXPECT_EQ(pContext->GetInstanceID(), 2005u); + EXPECT_EQ(pContext->GetInstanceID(), 2009u); pControl->Shutdown(true); EXPECT_EQ(pOperation->GetOperationState(), sdv::app::EAppOperationState::not_started); @@ -277,7 +279,9 @@ TEST(CoreLibrary_AppControl_NoEventHandler, Startup_Standalone_DefineInstance) EXPECT_EQ(pContext->GetInstanceID(), 0u); } -TEST(CoreLibrary_AppControl_NoEventHandler, Startup_External_DefineInstance) +// Currently disabled due to absence of server accepting connections. +// https://dev.azure.com/SW4ZF/AZP-431_DivDI_Vehicle_API/_workitems/edit/800643 +TEST(CoreLibrary_AppControl_NoEventHandler, DISABLED_Startup_External_DefineInstance) { sdv::app::IAppControl* pControl = sdv::core::GetCore(); ASSERT_NE(pControl, nullptr); @@ -290,11 +294,11 @@ TEST(CoreLibrary_AppControl_NoEventHandler, Startup_External_DefineInstance) EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::no_context); EXPECT_EQ(pContext->GetInstanceID(), 0u); - bool bResult = pControl->Startup("[Application]\nMode=\"External\"\nInstance=2005", nullptr); + bool bResult = pControl->Startup("[Application]\nMode=\"External\"\nInstance=2009", nullptr); EXPECT_TRUE(bResult); EXPECT_EQ(pOperation->GetOperationState(), sdv::app::EAppOperationState::running); EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::external); - EXPECT_EQ(pContext->GetInstanceID(), 2005u); + EXPECT_EQ(pContext->GetInstanceID(), 2009u); pControl->Shutdown(true); EXPECT_EQ(pOperation->GetOperationState(), sdv::app::EAppOperationState::not_started); @@ -315,16 +319,16 @@ TEST(CoreLibrary_AppControl_NoEventHandler, Startup_Isolated_DefineInstance) EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::no_context); EXPECT_EQ(pContext->GetInstanceID(), 0u); - std::string ssConfig = R"code([Application] + std::string ssConfig = R"toml([Application] Mode = "Isolated" -Instance = 2005 -Connection = ")code"; +Instance = 2009 +Connection = ")toml"; ssConfig += Base64EncodePlainText("test") + "\""; bool bResult = pControl->Startup(ssConfig, nullptr); EXPECT_TRUE(bResult); EXPECT_EQ(pOperation->GetOperationState(), sdv::app::EAppOperationState::running); EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::isolated); - EXPECT_EQ(pContext->GetInstanceID(), 2005u); + EXPECT_EQ(pContext->GetInstanceID(), 2009u); pControl->Shutdown(true); EXPECT_EQ(pOperation->GetOperationState(), sdv::app::EAppOperationState::not_started); @@ -345,11 +349,11 @@ TEST(CoreLibrary_AppControl_NoEventHandler, Startup_Main_DefineInstance) EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::no_context); EXPECT_EQ(pContext->GetInstanceID(), 0u); - bool bResult = pControl->Startup("[Application]\nMode=\"Main\"\nInstance=2005", nullptr); + bool bResult = pControl->Startup("[Application]\nMode=\"Main\"\nInstance=2009", nullptr); EXPECT_TRUE(bResult); EXPECT_EQ(pOperation->GetOperationState(), sdv::app::EAppOperationState::running); EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::main); - EXPECT_EQ(pContext->GetInstanceID(), 2005u); + EXPECT_EQ(pContext->GetInstanceID(), 2009u); pControl->Shutdown(true); EXPECT_EQ(pOperation->GetOperationState(), sdv::app::EAppOperationState::not_started); @@ -370,11 +374,11 @@ TEST(CoreLibrary_AppControl_NoEventHandler, Startup_Essential_DefineInstance) EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::no_context); EXPECT_EQ(pContext->GetInstanceID(), 0u); - bool bResult = pControl->Startup("[Application]\nMode=\"Essential\"\nInstance=2005", nullptr); + bool bResult = pControl->Startup("[Application]\nMode=\"Essential\"\nInstance=2009", nullptr); EXPECT_TRUE(bResult); EXPECT_EQ(pOperation->GetOperationState(), sdv::app::EAppOperationState::running); EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::essential); - EXPECT_EQ(pContext->GetInstanceID(), 2005u); + EXPECT_EQ(pContext->GetInstanceID(), 2009u); pControl->Shutdown(true); EXPECT_EQ(pOperation->GetOperationState(), sdv::app::EAppOperationState::not_started); @@ -395,11 +399,11 @@ TEST(CoreLibrary_AppControl_NoEventHandler, Startup_Maintenance_DefineInstance) EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::no_context); EXPECT_EQ(pContext->GetInstanceID(), 0u); - bool bResult = pControl->Startup("[Application]\nMode=\"Maintenance\"\nInstance=2005", nullptr); + bool bResult = pControl->Startup("[Application]\nMode=\"Maintenance\"\nInstance=2009", nullptr); EXPECT_TRUE(bResult); EXPECT_EQ(pOperation->GetOperationState(), sdv::app::EAppOperationState::running); EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::maintenance); - EXPECT_EQ(pContext->GetInstanceID(), 2005u); + EXPECT_EQ(pContext->GetInstanceID(), 2009u); pControl->Shutdown(true); EXPECT_EQ(pOperation->GetOperationState(), sdv::app::EAppOperationState::not_started); @@ -427,8 +431,10 @@ TEST(CoreLibrary_AppControl_NoEventHandler, RunLoop_Default) EXPECT_EQ(pContext->GetInstanceID(), 2007u); auto tpStart = std::chrono::high_resolution_clock::now(); - std::thread threadShutdownRequest([]() + sdv::core::secure_thread threadShutdownRequest([&]() { + while (pOperation->GetOperationState() != sdv::app::EAppOperationState::running) + std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(250)); RequestShutdown(2007u); }); @@ -462,8 +468,10 @@ TEST(CoreLibrary_AppControl_NoEventHandler, RunLoop_Standalone) EXPECT_EQ(pContext->GetInstanceID(), 2007u); auto tpStart = std::chrono::high_resolution_clock::now(); - std::thread threadShutdownRequest([]() + sdv::core::secure_thread threadShutdownRequest([&]() { + while (pOperation->GetOperationState() != sdv::app::EAppOperationState::running) + std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(250)); RequestShutdown(2007u); }); @@ -477,7 +485,9 @@ TEST(CoreLibrary_AppControl_NoEventHandler, RunLoop_Standalone) EXPECT_EQ(pContext->GetInstanceID(), 0u); } -TEST(CoreLibrary_AppControl_NoEventHandler, RunLoop_External) +// Currently disabled due to absence of server accepting connections. +// https://dev.azure.com/SW4ZF/AZP-431_DivDI_Vehicle_API/_workitems/edit/800643 +TEST(CoreLibrary_AppControl_NoEventHandler, DISABLED_RunLoop_External) { sdv::app::IAppControl* pControl = sdv::core::GetCore(); ASSERT_NE(pControl, nullptr); @@ -497,8 +507,10 @@ TEST(CoreLibrary_AppControl_NoEventHandler, RunLoop_External) EXPECT_EQ(pContext->GetInstanceID(), 2007u); auto tpStart = std::chrono::high_resolution_clock::now(); - std::thread threadShutdownRequest([]() + sdv::core::secure_thread threadShutdownRequest([&]() { + while (pOperation->GetOperationState() != sdv::app::EAppOperationState::running) + std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(250)); RequestShutdown(2007u); }); @@ -525,10 +537,10 @@ TEST(CoreLibrary_AppControl_NoEventHandler, RunLoop_Isolated) EXPECT_EQ(pContext->GetContextType(), sdv::app::EAppContext::no_context); EXPECT_EQ(pContext->GetInstanceID(), 0u); - std::string ssConfig = R"code([Application] + std::string ssConfig = R"toml([Application] Mode = "Isolated" Instance = 2007 -Connection = ")code"; +Connection = ")toml"; ssConfig += Base64EncodePlainText("test") + "\""; bool bResult = pControl->Startup(ssConfig, nullptr); EXPECT_TRUE(bResult); @@ -537,8 +549,10 @@ Connection = ")code"; EXPECT_EQ(pContext->GetInstanceID(), 2007u); auto tpStart = std::chrono::high_resolution_clock::now(); - std::thread threadShutdownRequest([]() + sdv::core::secure_thread threadShutdownRequest([&]() { + while (pOperation->GetOperationState() != sdv::app::EAppOperationState::running) + std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(250)); sdv::app::IAppShutdownRequest* pShutdownRequest = sdv::core::GetObject("AppControlService"); ASSERT_NE(pShutdownRequest, nullptr); @@ -574,8 +588,10 @@ TEST(CoreLibrary_AppControl_NoEventHandler, RunLoop_Main) EXPECT_EQ(pContext->GetInstanceID(), 2007u); auto tpStart = std::chrono::high_resolution_clock::now(); - std::thread threadShutdownRequest([]() + sdv::core::secure_thread threadShutdownRequest([&]() { + while (pOperation->GetOperationState() != sdv::app::EAppOperationState::running) + std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(250)); sdv::app::IAppShutdownRequest* pShutdownRequest = sdv::core::GetObject("AppControlService"); ASSERT_NE(pShutdownRequest, nullptr); @@ -611,8 +627,10 @@ TEST(CoreLibrary_AppControl_NoEventHandler, RunLoop_Essential) EXPECT_EQ(pContext->GetInstanceID(), 2007u); auto tpStart = std::chrono::high_resolution_clock::now(); - std::thread threadShutdownRequest([]() + sdv::core::secure_thread threadShutdownRequest([&]() { + while (pOperation->GetOperationState() != sdv::app::EAppOperationState::running) + std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(250)); RequestShutdown(2007u); }); diff --git a/tests/component_tests/config_install/module_install_test.cpp b/tests/component_tests/config_install/module_install_test.cpp index 1f0becc..9b94e0f 100644 --- a/tests/component_tests/config_install/module_install_test.cpp +++ b/tests/component_tests/config_install/module_install_test.cpp @@ -69,35 +69,26 @@ public: std::filesystem::path GetDefaultInstallDir(); }; -inline std::filesystem::path CModuleControl_Install::GetDefaultInstallDir() +std::filesystem::path CModuleControl_Install::GetDefaultInstallDir() { -// // The default directory for Windows is located at $ProgramData/ and for Posix at the executable. - std::filesystem::path pathInstallDir; -//#ifdef _WIN32 -// wchar_t* szPath = nullptr; -// HRESULT hr = SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_DEFAULT, 0, &szPath); -// if (SUCCEEDED(hr) && szPath) -// pathInstallDir = szPath; -// if (szPath) CoTaskMemFree(szPath); -//#elif defined __unix__ - pathInstallDir = GetExecDirectory(); -//#else -//#error OS is not supported! -//#endif // Append the installation directory with //. - pathInstallDir /= "2006"; - - return pathInstallDir; + return (GetExecDirectory() / "../../bin/2006").lexically_normal(); } TEST_F(CModuleControl_Install, MainApp_DefaultInstallDir) { sdv::app::CAppControl control; - bool bResult = control.Startup("[Application]\nMode=\"Main\"\nInstance=2006"); + bool bResult = control.Startup(R"toml([Application] +Mode = "Main" +Instance = 2006 + +[Console] +RedirectMon = true +)toml"); EXPECT_TRUE(bResult); // The default installation directory should be created. - EXPECT_TRUE(std::filesystem::exists(GetDefaultInstallDir())); + EXPECT_TRUE(std::filesystem::exists(control.GetComponentInstallDirectory())); control.Shutdown(); } @@ -106,10 +97,10 @@ TEST_F(CModuleControl_Install, IsolatedApp_DefaultInstallDir) { sdv::app::CAppControl control; std::string ssConnectionString = Base64EncodePlainText(""); - std::string ssConfig = R"code([Application] + std::string ssConfig = R"toml([Application] Mode = "Isolated" Instance = 2006 -Connection = ")code"; +Connection = ")toml"; ssConfig += Base64EncodePlainText("test") + "\""; bool bResult = control.Startup(ssConfig); EXPECT_TRUE(bResult); @@ -146,7 +137,13 @@ TEST_F(CModuleControl_Install, MainApp_BlockDefaultInstallDir) fstream.close(); sdv::app::CAppControl control; - bool bResult = control.Startup("[Application]\nMode=\"Main\"\nInstance=2006"); + bool bResult = control.Startup(R"toml([Application] +Mode = "Main" +Instance = 2006 + +[Console] +RedirectMon = true +)toml"); EXPECT_FALSE(bResult); // The default installation directory should be created. @@ -168,7 +165,9 @@ TEST_F(CModuleControl_Install, IsolatedApp_BlockDefaultInstallDir) fstream.close(); sdv::app::CAppControl control; - bool bResult = control.Startup("[Application]\nMode=\"Isolated\"\nInstance=2006"); + bool bResult = control.Startup(R"toml([Application] +Mode = "Isolated" +Instance = 2006)toml"); EXPECT_FALSE(bResult); // The default installation directory should be created. @@ -197,12 +196,12 @@ TEST_F(CModuleControl_Install, OtherApp_BlockDefaultInstallDir) TEST_F(CModuleControl_Install, MainApp_CustomInstallDirRelative) { sdv::app::CAppControl control; - bool bResult = control.Startup(R"code( + bool bResult = control.Startup(R"toml( [Application] Mode="Main" Instance=2006 InstallDir = "module_test1" -)code"); +)toml"); EXPECT_TRUE(bResult); // The default installation directory should be created. @@ -214,12 +213,12 @@ InstallDir = "module_test1" TEST_F(CModuleControl_Install, IsolatedApp_CustomInstallDirRelative) { sdv::app::CAppControl control; - bool bResult = control.Startup(std::string(R"code( + bool bResult = control.Startup(std::string(R"toml( [Application] Mode="Isolated" Instance=2006 InstallDir = "module_test1" -Connection = ")code") + Base64EncodePlainText("test") + "\""); +Connection = ")toml") + Base64EncodePlainText("test") + "\""); EXPECT_TRUE(bResult); // The default installation directory should be created. @@ -231,9 +230,9 @@ Connection = ")code") + Base64EncodePlainText("test") + "\""); TEST_F(CModuleControl_Install, OtherApp_CustomInstallDirRelative) { sdv::app::CAppControl control; - bool bResult = control.Startup(R"code([Application] + bool bResult = control.Startup(R"toml([Application] Install.Dir = "module_test1" -)code"); +)toml"); EXPECT_TRUE(bResult); // The default installation directory should be created. @@ -245,11 +244,11 @@ Install.Dir = "module_test1" TEST_F(CModuleControl_Install, MainApp_CustomInstallDirAbsolute) { sdv::app::CAppControl control; - bool bResult = control.Startup(std::string(R"code( + bool bResult = control.Startup(std::string(R"toml( [Application] Mode="Main" Instance=2006 -InstallDir = ")code") + (GetExecDirectory() / "module_test2").generic_u8string() + "\""); +InstallDir = ")toml") + (GetExecDirectory() / "module_test2").generic_u8string() + "\""); EXPECT_TRUE(bResult); // The default installation directory should be created. @@ -261,12 +260,12 @@ InstallDir = ")code") + (GetExecDirectory() / "module_test2").generic_u8string() TEST_F(CModuleControl_Install, IsolatedApp_CustomInstallDirAbsolute) { sdv::app::CAppControl control; - bool bResult = control.Startup(std::string(R"code( + bool bResult = control.Startup(std::string(R"toml( [Application] Mode="Isolated" Instance=2006 -InstallDir = ")code") + (GetExecDirectory() / "module_test2").generic_u8string() + "\"" + R"code( -Connection = ")code" + Base64EncodePlainText("test") + "\""); +InstallDir = ")toml") + (GetExecDirectory() / "module_test2").generic_u8string() + "\"" + R"toml( +Connection = ")toml" + Base64EncodePlainText("test") + "\""); EXPECT_TRUE(bResult); // The default installation directory should be created. @@ -278,8 +277,8 @@ Connection = ")code" + Base64EncodePlainText("test") + "\""); TEST_F(CModuleControl_Install, OtherApp_CustomInstallDirAbsolute) { sdv::app::CAppControl control; - bool bResult = control.Startup(std::string(R"code([Application] -InstallDir = ")code") + (GetExecDirectory() / "module_test2").generic_u8string() + "\""); + bool bResult = control.Startup(std::string(R"toml([Application] +InstallDir = ")toml") + (GetExecDirectory() / "module_test2").generic_u8string() + "\""); EXPECT_TRUE(bResult); // The default installation directory should be created. diff --git a/tests/component_tests/data_dispatch_service/data_dispatch_service_test.cpp b/tests/component_tests/data_dispatch_service/data_dispatch_service_test.cpp index 4233c03..24fe300 100644 --- a/tests/component_tests/data_dispatch_service/data_dispatch_service_test.cpp +++ b/tests/component_tests/data_dispatch_service/data_dispatch_service_test.cpp @@ -997,12 +997,12 @@ TEST(DataDispatchServiceTest, DirectRxTxSignalConcurrency) // Start the threads std::unique_lock lockStart(mtxStart); uint32_t uiCnt = 0; - std::thread rgPublishThreads[16]; - for (std::thread& rThread : rgPublishThreads) - rThread = std::thread(fnPublisher, uiCnt++); - std::thread rgConsumeThreads[16]; - for (std::thread& rThread : rgConsumeThreads) - rThread = std::thread(fnConsumer, uiCnt++); + sdv::core::secure_thread rgPublishThreads[16]; + for (sdv::core::secure_thread& rThread : rgPublishThreads) + rThread = sdv::core::secure_thread(fnPublisher, uiCnt++); + sdv::core::secure_thread rgConsumeThreads[16]; + for (sdv::core::secure_thread& rThread : rgConsumeThreads) + rThread = sdv::core::secure_thread(fnConsumer, uiCnt++); // Trigger the "GO" while (uiInitCnt < 32) std::this_thread::sleep_for(std::chrono::milliseconds(10)); @@ -1057,9 +1057,9 @@ TEST(DataDispatchServiceTest, DirectRxTxSignalConcurrency) // Wait for all threads to finalize appcontrol.SetConfigMode(); bShutdown = true; - for (std::thread& rThread : rgPublishThreads) + for (sdv::core::secure_thread& rThread : rgPublishThreads) if (rThread.joinable()) rThread.join(); - for (std::thread& rThread : rgConsumeThreads) + for (sdv::core::secure_thread& rThread : rgConsumeThreads) if (rThread.joinable()) rThread.join(); signalTx1a.Reset(); diff --git a/tests/component_tests/data_dispatch_service/transaction_test.cpp b/tests/component_tests/data_dispatch_service/transaction_test.cpp index 6f58d63..92dada6 100644 --- a/tests/component_tests/data_dispatch_service/transaction_test.cpp +++ b/tests/component_tests/data_dispatch_service/transaction_test.cpp @@ -1546,12 +1546,12 @@ TEST(DataDispatchServiceTest, TransactionalRxTxSignalConcurrency) // Start the threads std::unique_lock lockStart(mtxStart); uint32_t uiCnt = 0; - std::thread rgPublishThreads[16]; - for (std::thread& rThread : rgPublishThreads) - rThread = std::thread(fnPublisher, uiCnt++); - std::thread rgConsumeThreads[nConsumeThreads]; - for (std::thread& rThread :rgConsumeThreads) - rThread = std::thread(fnConsumer, uiCnt++); + sdv::core::secure_thread rgPublishThreads[16]; + for (sdv::core::secure_thread& rThread : rgPublishThreads) + rThread = sdv::core::secure_thread(fnPublisher, uiCnt++); + sdv::core::secure_thread rgConsumeThreads[nConsumeThreads]; + for (sdv::core::secure_thread& rThread :rgConsumeThreads) + rThread = sdv::core::secure_thread(fnConsumer, uiCnt++); // Trigger the "GO" while (uiInitCnt < 32) std::this_thread::sleep_for(std::chrono::milliseconds(10)); @@ -1630,11 +1630,11 @@ TEST(DataDispatchServiceTest, TransactionalRxTxSignalConcurrency) // Wait for all threads to finalize appcontrol.SetConfigMode(); bShutdownPublisher = true; - for (std::thread& rThread : rgPublishThreads) + for (sdv::core::secure_thread& rThread : rgPublishThreads) if (rThread.joinable()) rThread.join(); std::this_thread::sleep_for(std::chrono::milliseconds(1000)); bShutdownConsumer = true; - for (std::thread& rThread : rgConsumeThreads) + for (sdv::core::secure_thread& rThread : rgConsumeThreads) if (rThread.joinable()) rThread.join(); // Check consume ranges diff --git a/tests/component_tests/dbc_util/dbc_util_test.cpp b/tests/component_tests/dbc_util/dbc_util_test.cpp index 8b55ef4..70f019b 100644 --- a/tests/component_tests/dbc_util/dbc_util_test.cpp +++ b/tests/component_tests/dbc_util/dbc_util_test.cpp @@ -107,7 +107,7 @@ TEST(DbcUtilCanDLTest, SpontaneousTransmitBigEndian) // Start the data link CDbcStructDataLink dl; EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending); - dl.Initialize(""); + dl.Initialize(sdv::SObjectInfo()); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized); dl.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running); @@ -217,7 +217,7 @@ TEST(DbcUtilCanDLTest, SpontaneousTransmit64BitBigEndian) // Start the data link CDbcStructDataLink dl; EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending); - dl.Initialize(""); + dl.Initialize(sdv::SObjectInfo()); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized); dl.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running); @@ -361,7 +361,7 @@ TEST(DbcUtilCanDLTest, ReceiveBigEndian) // Start the data link CDbcStructDataLink dl; EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending); - dl.Initialize(""); + dl.Initialize(sdv::SObjectInfo()); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized); dl.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running); @@ -479,7 +479,7 @@ TEST(DbcUtilCanDLTest, Receive64BitBigEndian) // Start the data link CDbcStructDataLink dl; EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending); - dl.Initialize(""); + dl.Initialize(sdv::SObjectInfo()); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized); dl.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running); @@ -539,7 +539,7 @@ TEST(DbcUtilCanDLTest, SpontaneousTransmitLittleEndian) // Start the data link CDbcStructDataLink dl; EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending); - dl.Initialize(""); + dl.Initialize(sdv::SObjectInfo()); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized); dl.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running); @@ -649,7 +649,7 @@ TEST(DbcUtilCanDLTest, SpontaneousTransmit64BitLittleEndian) // Start the data link CDbcStructDataLink dl; EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending); - dl.Initialize(""); + dl.Initialize(sdv::SObjectInfo()); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized); dl.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running); @@ -793,7 +793,7 @@ TEST(DbcUtilCanDLTest, ReceiveLittleEndian) // Start the data link CDbcStructDataLink dl; EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending); - dl.Initialize(""); + dl.Initialize(sdv::SObjectInfo()); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized); dl.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running); @@ -911,7 +911,7 @@ TEST(DbcUtilCanDLTest, Receive64BitLittleEndian) // Start the data link CDbcStructDataLink dl; EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending); - dl.Initialize(""); + dl.Initialize(sdv::SObjectInfo()); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized); dl.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running); @@ -971,7 +971,7 @@ TEST(DbcUtilCanDLTest, SpontaneousTransactionalTransmitBigEndian) // Start the data link CDbcStructDataLink dl; EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending); - dl.Initialize(""); + dl.Initialize(sdv::SObjectInfo()); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized); dl.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running); @@ -1064,7 +1064,7 @@ TEST(DbcUtilCanDLTest, SpontaneousTransactionalTransmitLittleEndian) // Start the data link CDbcStructDataLink dl; EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending); - dl.Initialize(""); + dl.Initialize(sdv::SObjectInfo()); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized); dl.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running); @@ -1157,7 +1157,7 @@ TEST(DbcUtilCanDLTest, SpontaneousTransmitBigEndianAllDataTypes) // Start the data link CDbcStructDataLink dl; EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending); - dl.Initialize(""); + dl.Initialize(sdv::SObjectInfo()); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized); dl.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running); @@ -1443,7 +1443,7 @@ TEST(DbcUtilCanDLTest, ReceiveBigEndianAllDataTypes) // Start the data link CDbcStructDataLink dl; EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending); - dl.Initialize(""); + dl.Initialize(sdv::SObjectInfo()); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized); dl.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running); @@ -1629,7 +1629,7 @@ TEST(DbcUtilCanDLTest, SpontaneousTransmitLittleEndianAllDataTypes) // Start the data link CDbcStructDataLink dl; EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending); - dl.Initialize(""); + dl.Initialize(sdv::SObjectInfo()); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized); dl.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running); @@ -1915,7 +1915,7 @@ TEST(DbcUtilCanDLTest, ReceiveLittleEndianAllDataTypes) // Start the data link CDbcStructDataLink dl; EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending); - dl.Initialize(""); + dl.Initialize(sdv::SObjectInfo()); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized); dl.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running); @@ -2101,7 +2101,7 @@ TEST(DbcUtilCanDLTest, SpontaneousTransmitBigEndianScaledDataTypes) // Start the data link CDbcStructDataLink dl; EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending); - dl.Initialize(""); + dl.Initialize(sdv::SObjectInfo()); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized); dl.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running); @@ -2271,7 +2271,7 @@ TEST(DbcUtilCanDLTest, ReceiveBigEndianScaledDataType) // Start the data link CDbcStructDataLink dl; EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending); - dl.Initialize(""); + dl.Initialize(sdv::SObjectInfo()); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized); dl.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running); @@ -2378,7 +2378,7 @@ TEST(DbcUtilCanDLTest, SpontaneousTransmitLittleEndianScaledDataTypes) // Start the data link CDbcStructDataLink dl; EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending); - dl.Initialize(""); + dl.Initialize(sdv::SObjectInfo()); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized); dl.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running); @@ -2548,7 +2548,7 @@ TEST(DbcUtilCanDLTest, ReceiveLittleEndianScaledDataType) // Start the data link CDbcStructDataLink dl; EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending); - dl.Initialize(""); + dl.Initialize(sdv::SObjectInfo()); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized); dl.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running); @@ -2655,7 +2655,7 @@ TEST(DbcUtilCanDLTest, CyclicTransmit) // Start the data link CDbcStructDataLink dl; EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending); - dl.Initialize(""); + dl.Initialize(sdv::SObjectInfo()); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized); dl.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running); @@ -2804,7 +2804,7 @@ TEST(DbcUtilCanDLTest, CyclicIfActiveTransmit) // Start the data link CDbcStructDataLink dl; EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending); - dl.Initialize(""); + dl.Initialize(sdv::SObjectInfo()); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized); dl.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running); @@ -2971,7 +2971,7 @@ TEST(DbcUtilCanDLTest, CyclicAndSpontaneousTransmit) // Start the data link CDbcStructDataLink dl; EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending); - dl.Initialize(""); + dl.Initialize(sdv::SObjectInfo()); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized); dl.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running); @@ -3112,7 +3112,7 @@ TEST(DbcUtilCanDLTest, SpontaneousDelayTransmit) // Start the data link CDbcStructDataLink dl; EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending); - dl.Initialize(""); + dl.Initialize(sdv::SObjectInfo()); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized); dl.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running); @@ -3250,7 +3250,7 @@ TEST(DbcUtilCanDLTest, CyclicAndSpontaneousDelayTransmit) // Start the data link CDbcStructDataLink dl; EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending); - dl.Initialize(""); + dl.Initialize(sdv::SObjectInfo()); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized); dl.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running); @@ -3394,7 +3394,7 @@ TEST(DbcUtilCanDLTest, CyclicIfActiveAndSpontaneousTransmit) // Start the data link CDbcStructDataLink dl; EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending); - dl.Initialize(""); + dl.Initialize(sdv::SObjectInfo()); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized); dl.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running); diff --git a/tests/component_tests/param_utils/CMakeLists.txt b/tests/component_tests/param_utils/CMakeLists.txt new file mode 100644 index 0000000..be64482 --- /dev/null +++ b/tests/component_tests/param_utils/CMakeLists.txt @@ -0,0 +1,36 @@ +#******************************************************************************* +# Copyright (c) 2025-2026 ZF Friedrichshafen AG +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# +# Contributors: +# Erik Verhoeven - initial API and implementation +#******************************************************************************* + +# Define project +project (ComponentTest_ParamUtils VERSION 1.0 LANGUAGES CXX) + +# Data maneger executable +add_executable(ComponentTest_ParamUtils + "main.cpp" + "get_parameter.cpp" + "resolve_text.cpp" + ) + +target_link_libraries(ComponentTest_ParamUtils ${CMAKE_DL_LIBS} GTest::GTest) + +# Add the ParamUtils component test +add_test(NAME ComponentTest_ParamUtils COMMAND ComponentTest_ParamUtils WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + +# Execute the test +add_custom_command(TARGET ComponentTest_ParamUtils POST_BUILD + COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake "$" --gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ComponentTest_ParamUtils.xml + VERBATIM +) + +# Build dependencies +add_dependencies(ComponentTest_ParamUtils dependency_sdv_components) diff --git a/tests/component_tests/param_utils/get_parameter.cpp b/tests/component_tests/param_utils/get_parameter.cpp new file mode 100644 index 0000000..0aefa87 --- /dev/null +++ b/tests/component_tests/param_utils/get_parameter.cpp @@ -0,0 +1,213 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#include +#include +#include + +TEST(ParamUtils, GetParameter_FailParamInterface) +{ + sdv::app::CAppControl control; + bool bResult = control.Startup("[Application]\nMode=\"Main\"\nInstance=2010"); + ASSERT_TRUE(bResult); + + // Get the app settings service + sdv::TInterfaceAccessPtr ptrSystemSettings = sdv::core::GetObject("AppSettingsService"); + ASSERT_TRUE(ptrSystemSettings); + + // Request the IParameters interface + sdv::IParameters* pParameters = ptrSystemSettings.GetInterface(); + ASSERT_NE(pParameters, nullptr); + + // Invalid parameters interface + sdv::any_t anyFail = sdv::core::GetParameter(nullptr, "Application.InstallDir"); + EXPECT_TRUE(anyFail.empty()); + bool bExcept = false; + try + { + sdv::core::GetParameter(nullptr, "Application.InstallDir", false); + } + catch (const std::runtime_error&) + { + bExcept = true; + } + EXPECT_TRUE(bExcept); + + // Invalid parameters name + anyFail = sdv::core::GetParameter(pParameters, "ParamDoesNotExist"); + EXPECT_TRUE(anyFail.empty()); + bExcept = false; + try + { + sdv::core::GetParameter(pParameters, "ParamDoesNotExist", false); + } + catch (const std::runtime_error&) + { + bExcept = true; + } + EXPECT_TRUE(bExcept); + + control.Shutdown(); +} + +TEST(ParamUtils, GetParameter_FailObjectInterface) +{ + sdv::app::CAppControl control; + bool bResult = control.Startup("[Application]\nMode=\"Main\"\nInstance=2010"); + ASSERT_TRUE(bResult); + + // Get the app settings service + sdv::TInterfaceAccessPtr ptrSystemSettings = sdv::core::GetObject("AppSettingsService"); + ASSERT_TRUE(ptrSystemSettings); + + // Invalid object interface + sdv::any_t anyFail = sdv::core::GetParameter(sdv::TInterfaceAccessPtr(), "Application.InstallDir"); + EXPECT_TRUE(anyFail.empty()); + bool bExcept = false; + try + { + sdv::core::GetParameter(sdv::TInterfaceAccessPtr(), "Application.InstallDir", false); + } + catch (const std::runtime_error&) + { + bExcept = true; + } + EXPECT_TRUE(bExcept); + + // Invalid parameters name + anyFail = sdv::core::GetParameter(ptrSystemSettings, "ParamDoesNotExist"); + EXPECT_TRUE(anyFail.empty()); + bExcept = false; + try + { + sdv::core::GetParameter(ptrSystemSettings, "ParamDoesNotExist", false); + } + catch (const std::runtime_error&) + { + bExcept = true; + } + EXPECT_TRUE(bExcept); + + control.Shutdown(); +} + +TEST(ParamUtils, GetParameter_FailObjectName) +{ + sdv::app::CAppControl control; + bool bResult = control.Startup("[Application]\nMode=\"Main\"\nInstance=2010"); + ASSERT_TRUE(bResult); + + // Invalid object name + sdv::any_t anyFail = sdv::core::GetParameter("InvalidObject", "Application.InstallDir"); + EXPECT_TRUE(anyFail.empty()); + bool bExcept = false; + try + { + sdv::core::GetParameter("InvalidObject", "Application.InstallDir", false); + } + catch (const std::runtime_error&) + { + bExcept = true; + } + EXPECT_TRUE(bExcept); + + // Invalid parameters name + anyFail = sdv::core::GetParameter("AppSettingsService", "ParamDoesNotExist"); + EXPECT_TRUE(anyFail.empty()); + bExcept = false; + try + { + sdv::core::GetParameter("AppSettingsService", "ParamDoesNotExist", false); + } + catch (const std::runtime_error&) + { + bExcept = true; + } + EXPECT_TRUE(bExcept); + + control.Shutdown(); +} + +TEST(ParamUtils, GetParameter_ParamInterface) +{ + sdv::app::CAppControl control; + bool bResult = control.Startup("[Application]\nMode=\"Main\"\nInstance=2010"); + ASSERT_TRUE(bResult); + + // Get the app settings service + sdv::TInterfaceAccessPtr ptrSystemSettings = sdv::core::GetObject("AppSettingsService"); + ASSERT_TRUE(ptrSystemSettings); + + // Request the IParameters interface + sdv::IParameters* pParameters = ptrSystemSettings.GetInterface(); + ASSERT_NE(pParameters, nullptr); + + // Request the install directory through parameters + sdv::any_t anyInstall = sdv::core::GetParameter(pParameters, "Application.InstallDir"); + EXPECT_FALSE(anyInstall.empty()); + EXPECT_EQ(std::filesystem::relative(anyInstall.get(), control.GetAppDirectory()), "../../bin/2010"); + + control.Shutdown(); +} + +TEST(ParamUtils, GetParameter_ObjectInterface) +{ + sdv::app::CAppControl control; + bool bResult = control.Startup("[Application]\nMode=\"Main\"\nInstance=2010"); + ASSERT_TRUE(bResult); + + // Get the app settings service + sdv::TInterfaceAccessPtr ptrSystemSettings = sdv::core::GetObject("AppSettingsService"); + ASSERT_TRUE(ptrSystemSettings); + + // Request the install directory through parameters + sdv::any_t anyInstall = sdv::core::GetParameter(ptrSystemSettings, "Application.InstallDir"); + EXPECT_FALSE(anyInstall.empty()); + EXPECT_EQ(std::filesystem::relative(anyInstall.get(), control.GetAppDirectory()), "../../bin/2010"); + + control.Shutdown(); +} + +TEST(ParamUtils, GetParameter_ObjectName) +{ + sdv::app::CAppControl control; + bool bResult = control.Startup("[Application]\nMode=\"Main\"\nInstance=2010"); + ASSERT_TRUE(bResult); + + // Request the install directory through parameters + sdv::any_t anyInstall = sdv::core::GetParameter("AppSettingsService", "Application.InstallDir"); + EXPECT_FALSE(anyInstall.empty()); + EXPECT_EQ(std::filesystem::relative(anyInstall.get(), control.GetAppDirectory()), "../../bin/2010"); + + control.Shutdown(); +} + +TEST(ParamUtils, GetParameter_EnumExpand) +{ + sdv::app::CAppControl control; + bool bResult = control.Startup("[Application]\nMode=\"Main\"\nInstance=2010"); + ASSERT_TRUE(bResult); + + // Request the mode through parameters + sdv::any_t anyMode = sdv::core::GetParameter("AppSettingsService", "Application.Mode"); + EXPECT_FALSE(anyMode.empty()); + EXPECT_EQ(static_cast(anyMode), static_cast(sdv::app::EAppContext::main)); + + // Request the expanded mode through parameters + std::string ssMode = sdv::core::GetParameterExpand("AppSettingsService", "Application.Mode"); + EXPECT_FALSE(ssMode.empty()); + EXPECT_EQ(ssMode, "Main"); + + control.Shutdown(); +} + diff --git a/tests/unit_tests/toml_parser/generate_toml_miscellaneous.cpp b/tests/component_tests/param_utils/main.cpp similarity index 64% rename from tests/unit_tests/toml_parser/generate_toml_miscellaneous.cpp rename to tests/component_tests/param_utils/main.cpp index f3b56fc..3da742d 100644 --- a/tests/unit_tests/toml_parser/generate_toml_miscellaneous.cpp +++ b/tests/component_tests/param_utils/main.cpp @@ -12,10 +12,16 @@ ********************************************************************************/ #include +#include "../../../global/process_watchdog.h" -#include "../../../sdv_services/core/toml_parser/parser_node_toml.h" -#include "../../../sdv_services/core/toml_parser/parser_toml.h" +#if defined(_WIN32) && defined(_UNICODE) +extern "C" int wmain(int argc, wchar_t* argv[]) +#else +extern "C" int main(int argc, char* argv[]) +#endif +{ + CProcessWatchdog watchdog; -// Test TODO: -// Shift nodes up and down within one container -// Remove formatting from node \ No newline at end of file + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/component_tests/param_utils/resolve_text.cpp b/tests/component_tests/param_utils/resolve_text.cpp new file mode 100644 index 0000000..8cd8160 --- /dev/null +++ b/tests/component_tests/param_utils/resolve_text.cpp @@ -0,0 +1,168 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#include +#include +#include + +TEST(ParamUtils, ResolveText_FailObjectName) +{ + sdv::app::CAppControl control; + bool bResult = control.Startup("[Application]\nMode=\"Main\"\nInstance=2010"); + ASSERT_TRUE(bResult); + + // Invalid parameters interface + std::string ssText = sdv::core::ResolveText("$(InvalidObject:Application.InstallDir)"); + EXPECT_TRUE(ssText.empty()); + bool bExcept = false; + try + { + sdv::core::ResolveText("$(InvalidObject:Application.InstallDir)", true, false); + } + catch (const std::runtime_error&) + { + bExcept = true; + } + EXPECT_TRUE(bExcept); + + // Invalid parameter name + ssText = sdv::core::ResolveText("$(AppSettingsService:ParamDoesNotExist)"); + EXPECT_TRUE(ssText.empty()); + bExcept = false; + try + { + sdv::core::ResolveText("$(AppSettingsService:ParamDoesNotExist)", true, false); + } + catch (const std::runtime_error&) + { + bExcept = true; + } + EXPECT_TRUE(bExcept); + + control.Shutdown(); +} + +TEST(ParamUtils, ResolveText_EscapingDollarCharacter) +{ + sdv::app::CAppControl control; + bool bResult = control.Startup("[Application]\nMode=\"Main\"\nInstance=2010"); + ASSERT_TRUE(bResult); + + // Not escaping $ - this is allowed! + std::string ssText = sdv::core::ResolveText("There is a $ in this text."); + EXPECT_FALSE(ssText.empty()); + EXPECT_EQ(ssText, "There is a $ in this text."); + bool bExcept = false; + ssText.clear(); + try + { + ssText = sdv::core::ResolveText("There is a $ in this text.", true, false); + } + catch (const std::runtime_error&) + { + bExcept = true; + } + EXPECT_FALSE(bExcept); + EXPECT_EQ(ssText, "There is a $ in this text."); + + // Missing (-character - this is the same as not escaping $ + ssText = sdv::core::ResolveText("$AppSettingsService:Application.InstallDir)"); + EXPECT_FALSE(ssText.empty()); + EXPECT_EQ(ssText, "$AppSettingsService:Application.InstallDir)"); + bExcept = false; + ssText.clear(); + try + { + ssText = sdv::core::ResolveText("$AppSettingsService:Application.InstallDir)", true, false); + } + catch (const std::runtime_error&) + { + bExcept = true; + } + EXPECT_FALSE(bExcept); + EXPECT_EQ(ssText, "$AppSettingsService:Application.InstallDir)"); + + // Missing )-character - this is an error + ssText = sdv::core::ResolveText("$(AppSettingsService:Application.InstallDir"); + EXPECT_TRUE(ssText.empty()); + bExcept = false; + try + { + sdv::core::ResolveText("$(AppSettingsService:Application.InstallDir", true, false); + } + catch (const std::runtime_error&) + { + bExcept = true; + } + EXPECT_TRUE(bExcept); + + // Invalid object-param pair + ssText = sdv::core::ResolveText("$(Application.InstallDir)"); + EXPECT_TRUE(ssText.empty()); + bExcept = false; + try + { + sdv::core::ResolveText("$(Application.InstallDir)", true, false); + } + catch (const std::runtime_error&) + { + bExcept = true; + } + EXPECT_TRUE(bExcept); + + // Resolvinf during first iteration + ssText = sdv::core::ResolveText("$(AppSettingsService:Application.Mode)"); + EXPECT_EQ(ssText, "Main"); + + // Resolving during second iteration + ssText = sdv::core::ResolveText("\\$(AppSettingsService:Application.Mode)"); + EXPECT_EQ(ssText, "Main"); + + // No further iteration + ssText = sdv::core::ResolveText("\\$(AppSettingsService:Application.Mode)", false); + EXPECT_EQ(ssText, "$(AppSettingsService:Application.Mode)"); + + control.Shutdown(); +} + +TEST(ParamUtils, ResolveText_Object) +{ + sdv::app::CAppControl control; + bool bResult = control.Startup("[Application]\nMode=\"Main\"\nInstance=2010"); + ASSERT_TRUE(bResult); + + // Request the install directory through parameters + std::string ssText = sdv::core::ResolveText("$(AppSettingsService:Application.InstallDir)/MyPackage"); + EXPECT_FALSE(ssText.empty()); + EXPECT_EQ(std::filesystem::relative(ssText, control.GetAppDirectory()), "../../bin/2010/MyPackage"); + + // Request the install directory through parameters + ssText = sdv::core::ResolveText("The application mode is \"$( AppSettingsService : Application.Mode )\" and the instance numbver is $(AppSettingsService:Application.Instance)."); + EXPECT_FALSE(ssText.empty()); + EXPECT_EQ(ssText, "The application mode is \"Main\" and the instance numbver is 2010."); + + ssText = sdv::core::ResolveText("The application mode is \"$(AppSettingsService:Application.Mode)\" and the instance number " + "is $(AppSettingsService:Application.Instance)."); + EXPECT_FALSE(ssText.empty()); + EXPECT_EQ(ssText, "The application mode is \"Main\" and the instance number is 2010."); + + // Request the install directory through parameters + ssText = sdv::core::ResolveText("Valid text with a \\$ in the text..."); + EXPECT_FALSE(ssText.empty()); + EXPECT_EQ(ssText, "Valid text with a $ in the text..."); + + control.Shutdown(); +} + +// TODO: Recursive test +// e.g. \$($(MyObject1:Name):$(MyObject2:ParamName)) diff --git a/tests/component_tests/repository/CMakeLists.txt b/tests/component_tests/repository/CMakeLists.txt index eff7440..199126a 100644 --- a/tests/component_tests/repository/CMakeLists.txt +++ b/tests/component_tests/repository/CMakeLists.txt @@ -57,7 +57,7 @@ set_target_properties(ComponentTest_Repository_test_module PROPERTIES SUFFIX ".s # Execute the installation helper utility to create an installation manifest for the core files. add_custom_target(ComponentTest_Repository_install_manifest - COMMAND "$" DIRECT_INSTALL ComponentTest_Repository -T. --instance2005 "$" "$" "-I$" --overwrite --user_config+TestObject_ComplexHelloService+TestObject_HelloUtility --interface_config+Example_Object+Example_Object_2+TestObject_HelloDevice+TestObject_BasicHelloService+TestObject_SystemHelloService + COMMAND "$" DIRECT_INSTALL ComponentTest_Repository --instance2005 "$" "$" "-I$" --overwrite --user_config+TestObject_ComplexHelloService+TestObject_HelloUtility --interface_config+Example_Object+Example_Object_2+TestObject_HelloDevice+TestObject_BasicHelloService+TestObject_SystemHelloService DEPENDS ComponentTest_Repository_test_module ComponentTest_Repository_ps WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} ) diff --git a/tests/component_tests/repository/isolation_test.cpp b/tests/component_tests/repository/isolation_test.cpp index 3cfe7eb..02e7fc2 100644 --- a/tests/component_tests/repository/isolation_test.cpp +++ b/tests/component_tests/repository/isolation_test.cpp @@ -44,14 +44,18 @@ public: */ virtual void SetUp() { - bool bRet = Startup(R"code( + bool bRet = Startup(R"toml( [LogHandler] ViewFilter = "Fatal" [Application] Mode = "Main" Instance = 2005 -)code"); + +[Console] +Report = "Silent" +RedirectMon = true +)toml"); if (!bRet) std::cout << "Failed to start the application for instance 2005." << std::endl; } diff --git a/tests/component_tests/repository/repository_test.cpp b/tests/component_tests/repository/repository_test.cpp index 3781e42..410fc3c 100644 --- a/tests/component_tests/repository/repository_test.cpp +++ b/tests/component_tests/repository/repository_test.cpp @@ -42,7 +42,7 @@ public: SDV_INTERFACE_ENTRY(sdv::IObjectControl) END_SDV_INTERFACE_MAP() - virtual void Initialize([[maybe_unused]] const sdv::u8string& ssObjectConfig) override + virtual void Initialize([[maybe_unused]] const sdv::SObjectInfo& sObjectInfo) override { FAIL() << "Error: Initialize should not be called by Repo Service!"; // m_eObjectState = sdv::EObjectState::initialization_failure; @@ -83,13 +83,13 @@ public: TEST(RepositoryTest, LoadNonexistentModule) { - sdv::app::CAppControl control(R"code( + sdv::app::CAppControl control(R"toml( [LogHandler] ViewFilter = "Fatal" [Application] Mode = "Essential" -)code"); +)toml"); ASSERT_TRUE(control.IsRunning()); control.SetConfigMode(); @@ -105,13 +105,13 @@ Mode = "Essential" TEST(RepositoryTest, CreateNonexistantClass) { - sdv::app::CAppControl control(R"code( + sdv::app::CAppControl control(R"toml( [LogHandler] ViewFilter = "Fatal" [Application] Mode = "Essential" -)code"); +)toml"); ASSERT_TRUE(control.IsRunning()); control.SetConfigMode(); @@ -129,13 +129,13 @@ Mode = "Essential" TEST(RepositoryTest, GetNonexistantObject) { - sdv::app::CAppControl control(R"code( + sdv::app::CAppControl control(R"toml( [LogHandler] ViewFilter = "Fatal" [Application] Mode = "Essential" -)code"); +)toml"); ASSERT_TRUE(control.IsRunning()); control.SetConfigMode(); @@ -158,13 +158,13 @@ Mode = "Essential" TEST(RepositoryTest, InstantiateAndGet) { - sdv::app::CAppControl control(R"code( + sdv::app::CAppControl control(R"toml( [LogHandler] ViewFilter = "Fatal" [Application] Mode = "Essential" -)code"); +)toml"); ASSERT_TRUE(control.IsRunning()); control.SetConfigMode(); @@ -189,13 +189,13 @@ Mode = "Essential" TEST(RepositoryTest, InstantiateInitFail) { - sdv::app::CAppControl control(R"code( + sdv::app::CAppControl control(R"toml( [LogHandler] ViewFilter = "Fatal" [Application] Mode = "Essential" -)code"); +)toml"); ASSERT_TRUE(control.IsRunning()); control.SetConfigMode(); @@ -218,13 +218,13 @@ Mode = "Essential" TEST(RepositoryTest, InstantiateDuplicateCreateObjectName) { - sdv::app::CAppControl control(R"code( + sdv::app::CAppControl control(R"toml( [LogHandler] ViewFilter = "Fatal" [Application] Mode = "Essential" -)code"); +)toml"); ASSERT_TRUE(control.IsRunning()); control.SetConfigMode(); @@ -255,13 +255,13 @@ Mode = "Essential" TEST(RepositoryTest, InstantiateDuplicateExternalObjectName) { - sdv::app::CAppControl control(R"code( + sdv::app::CAppControl control(R"toml( [LogHandler] ViewFilter = "Fatal" [Application] Mode = "Essential" -)code"); +)toml"); ASSERT_TRUE(control.IsRunning()); control.SetConfigMode(); @@ -290,13 +290,13 @@ Mode = "Essential" TEST(RepositoryTest, InstantiateDuplicateObjectNameMixed) { - sdv::app::CAppControl control(R"code( + sdv::app::CAppControl control(R"toml( [LogHandler] ViewFilter = "Fatal" [Application] Mode = "Essential" -)code"); +)toml"); ASSERT_TRUE(control.IsRunning()); control.SetConfigMode(); @@ -327,13 +327,13 @@ Mode = "Essential" TEST(RepositoryTest, InstantiateTwoNamedInstances) { - sdv::app::CAppControl control(R"code( + sdv::app::CAppControl control(R"toml( [LogHandler] ViewFilter = "Fatal" [Application] Mode = "Essential" -)code"); +)toml"); ASSERT_TRUE(control.IsRunning()); control.SetConfigMode(); @@ -365,13 +365,13 @@ Mode = "Essential" TEST(RepositoryTest, ChainCreateSimple) { - sdv::app::CAppControl control(R"code( + sdv::app::CAppControl control(R"toml( [LogHandler] ViewFilter = "Fatal" [Application] Mode = "Essential" -)code"); +)toml"); ASSERT_TRUE(control.IsRunning()); control.SetConfigMode(); @@ -398,13 +398,13 @@ Mode = "Essential" TEST(RepositoryTest, ChainCreateParallel) { - sdv::app::CAppControl control(R"code( + sdv::app::CAppControl control(R"toml( [LogHandler] ViewFilter = "Fatal" [Application] Mode = "Essential" -)code"); +)toml"); ASSERT_TRUE(control.IsRunning()); control.SetConfigMode(); @@ -417,43 +417,47 @@ Mode = "Essential" ASSERT_TRUE(pModuleControl->Load((GetExecDirectory() / "ComponentTest_Repository_test_module.sdv").generic_u8string())); - std::thread testThread([pRepositoryControl, pObjectAccess]() + sdv::core::secure_thread testThread( + [pRepositoryControl, pObjectAccess]() { for (uint32_t i = 0; i < LoopCount; ++i) { std::string count = std::to_string(i); - bool bRes = pRepositoryControl->CreateObject("TestObject_CreateChain", "Bar_" + count, - "chained_object = \"BarFoo_" + count + "\""); + bool bRes = pRepositoryControl->CreateObject( + "TestObject_CreateChain", "Bar_" + count, "chained_object = \"BarFoo_" + count + "\""); EXPECT_TRUE(bRes); EXPECT_NE(nullptr, pObjectAccess->GetObject("BarFoo_" + count)); } }); + // TEMP EVE 05.05.2026: The parallel execution of the creation threads causes a sporadic crash. + // See: https://dev.azure.com/SW4ZF/AZP-431_DivDI_Vehicle_API/_workitems/edit/799698 + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + for (uint32_t i = 0; i < LoopCount; ++i) { std::string count = std::to_string(i); - bool bRes = - pRepositoryControl->CreateObject("TestObject_CreateChain", "Foo_" + count, "chained_object =\"FooBar_" + count + "\""); + bool bRes = pRepositoryControl->CreateObject( + "TestObject_CreateChain", "Foo_" + count, "chained_object =\"FooBar_" + count + "\""); EXPECT_TRUE(bRes); EXPECT_NE(nullptr, pObjectAccess->GetObject("FooBar_" + count)); } testThread.join(); - control.SetRunningMode(); control.Shutdown(); } TEST(RepositoryTest, ChainCreateParallelLock) { - sdv::app::CAppControl control(R"code( + sdv::app::CAppControl control(R"toml( [LogHandler] ViewFilter = "Fatal" [Application] Mode = "Essential" -)code"); +)toml"); ASSERT_TRUE(control.IsRunning()); control.SetConfigMode(); @@ -474,7 +478,7 @@ Mode = "Essential" //attempt to create deadlock by taking lock during initialize and by taking lock before call to create object - std::thread testThread([&]() + sdv::core::secure_thread testThread([&]() { for (uint32_t i = 0; i < LoopCount; ++i) { @@ -507,13 +511,13 @@ Mode = "Essential" TEST(RepositoryTest, ChainCreateParallelThreadLock) { - sdv::app::CAppControl control(R"code( + sdv::app::CAppControl control(R"toml( [LogHandler] ViewFilter = "Fatal" [Application] Mode = "Essential" -)code"); +)toml"); ASSERT_TRUE(control.IsRunning()); control.SetConfigMode(); @@ -534,7 +538,7 @@ Mode = "Essential" //attempt to create deadlock by taking lock during initialize and by taking lock before call to create object - std::thread testThread([&]() + sdv::core::secure_thread testThread([&]() { for (uint32_t i = 0; i < LoopCount; ++i) { @@ -567,13 +571,13 @@ Mode = "Essential" TEST(RepositoryTest, InstantiateDuringShutdown) { - sdv::app::CAppControl control(R"code( + sdv::app::CAppControl control(R"toml( [LogHandler] ViewFilter = "Fatal" [Application] Mode = "Essential" -)code"); +)toml"); ASSERT_TRUE(control.IsRunning()); control.SetConfigMode(); @@ -598,13 +602,13 @@ Mode = "Essential" TEST(RepositoryTest, CreateUtility) { - sdv::app::CAppControl control(R"code( + sdv::app::CAppControl control(R"toml( [LogHandler] ViewFilter = "Fatal" [Application] Mode = "Essential" -)code"); +)toml"); ASSERT_TRUE(control.IsRunning()); control.SetConfigMode(); @@ -621,14 +625,18 @@ TEST(RepositoryTest, MainApplication_GetInstalledAndLoadedComponent) { // Start the app control. The application automatically loads the installation manifests and the application configuration, // starting automatically the objects. - sdv::app::CAppControl control(R"code( + sdv::app::CAppControl control(R"toml( [LogHandler] ViewFilter = "Fatal" [Application] Mode = "Main" Instance = 2005 -)code"); + +[Console] +Report = "Silent" +RedirectMon = true +)toml"); ASSERT_TRUE(control.IsRunning()); control.SetConfigMode(); @@ -644,7 +652,7 @@ TEST(RepositoryTest, MainApplication_GetInstalledComponent) { // Start the app control. The application automatically loads the installation manifests, but since there is no application // configuration, it doesn't load the objects. - sdv::app::CAppControl control(R"code( + sdv::app::CAppControl control(R"toml( [LogHandler] ViewFilter = "Fatal" @@ -652,7 +660,11 @@ ViewFilter = "Fatal" Mode = "Main" Instance = 2005 AppConfig = "" # override application config -)code"); + +[Console] +Report = "Silent" +RedirectMon = true +)toml"); ASSERT_TRUE(control.IsRunning()); control.SetConfigMode(); diff --git a/tests/component_tests/repository/test_component.cpp b/tests/component_tests/repository/test_component.cpp index 414c9df..78c71ea 100644 --- a/tests/component_tests/repository/test_component.cpp +++ b/tests/component_tests/repository/test_component.cpp @@ -306,7 +306,7 @@ public: } }; - std::thread testThread(fun); + sdv::core::secure_thread testThread(fun); testThread.join(); diff --git a/tests/include/test_watchdog.h b/tests/include/test_watchdog.h index af8cc32..e38537e 100644 --- a/tests/include/test_watchdog.h +++ b/tests/include/test_watchdog.h @@ -64,7 +64,7 @@ public: */ CProcessWatchdog(int64_t iWatchdogTimeS = 300ll) { - m_threadWatchdog = std::thread(&CProcessWatchdog::WatchdogThreadFunc, this, iWatchdogTimeS ); + m_threadWatchdog = sdv::core::secure_thread(&CProcessWatchdog::WatchdogThreadFunc, this, iWatchdogTimeS ); } /** @@ -110,8 +110,8 @@ private: } } - std::atomic_bool m_bTerminateWatchdog = false; ///< When set, allows the thread to terminate. - std::thread m_threadWatchdog; ///< The watchdog thread. + std::atomic_bool m_bTerminateWatchdog = false; ///< When set, allows the thread to terminate. + sdv::core::secure_thread m_threadWatchdog; ///< The watchdog thread. }; #endif // !defined TEST_WATCHDOG_H diff --git a/tests/manual_tests/silkit_can_com_tests/include/can_com_test_helper.h b/tests/manual_tests/silkit_can_com_tests/include/can_com_test_helper.h index e308798..02d4865 100644 --- a/tests/manual_tests/silkit_can_com_tests/include/can_com_test_helper.h +++ b/tests/manual_tests/silkit_can_com_tests/include/can_com_test_helper.h @@ -27,9 +27,9 @@ class CTestCanSilKit : public CCANSilKit { public: - virtual void Initialize(const sdv::u8string& ssObjectConfig) override + virtual void Initialize(const sdv::SObjectInfo& sObjectInfo) override { - return CCANSilKit::Initialize(ssObjectConfig); + return CCANSilKit::Initialize(sObjectInfo); } virtual sdv::EObjectState GetObjectState() const override diff --git a/tests/manual_tests/silkit_can_com_tests/src/can_com_manual_test_silkit.cpp b/tests/manual_tests/silkit_can_com_tests/src/can_com_manual_test_silkit.cpp index 779b121..032447e 100644 --- a/tests/manual_tests/silkit_can_com_tests/src/can_com_manual_test_silkit.cpp +++ b/tests/manual_tests/silkit_can_com_tests/src/can_com_manual_test_silkit.cpp @@ -31,9 +31,9 @@ class CANSilkitTest : public ::testing::Test class CTestCANSilkit : public CCANSilKit { public: - virtual void Initialize(const sdv::u8string& ssObjectConfig) override + virtual void Initialize(const sdv::SObjectInfo& sObjectInfo) override { - CCANSilKit::Initialize(ssObjectConfig); + CCANSilKit::Initialize(sObjectInfo); } virtual void Shutdown() override @@ -113,7 +113,9 @@ bool InitializeAppControl(sdv::app::CAppControl* appcontrol, const std::string& void InitializeCanComObject(CTestCANSilkit& canComObj, const std::string config, MockCANReceiver& mockRcv) { - ASSERT_NO_THROW(canComObj.Initialize(config.c_str())); + sdv::SObjectInfo sObjectInfo{}; + sObjectInfo.ssConfig = config; + ASSERT_NO_THROW(canComObj.Initialize(sObjectInfo)); EXPECT_EQ(canComObj.GetObjectState(), sdv::EObjectState::initialized); ASSERT_NO_THROW(canComObj.SetOperationMode(sdv::EOperationMode::configuring)); ASSERT_NO_THROW(canComObj.RegisterReceiver(&mockRcv)); @@ -142,7 +144,8 @@ TEST_F(CANSilkitTest, ValidConfigString) sdv::app::CAppControl appControl; appControl.Startup(""); - sdv::u8string ssObjectConfig = + sdv::SObjectInfo sObjectInfo{}; + sObjectInfo.ssConfig = R"( DebugInfo = true SyncMode = true @@ -156,7 +159,7 @@ TEST_F(CANSilkitTest, ValidConfigString) }""" )"; CTestCANSilkit canComObj; - ASSERT_NO_THROW(canComObj.Initialize(ssObjectConfig.c_str())); + ASSERT_NO_THROW(canComObj.Initialize(sObjectInfo)); ASSERT_EQ(canComObj.GetObjectState(), sdv::EObjectState::initialized); ASSERT_NO_THROW(canComObj.Send(testMsg, 0)); @@ -170,7 +173,8 @@ TEST_F(CANSilkitTest, InvalidConfigIdentifier) sdv::app::CAppControl appControl; appControl.Startup(""); - sdv::u8string ssObjectConfig = + sdv::SObjectInfo sObjectInfo{}; + sObjectInfo.ssConfig = R"( DebugInfo = true SyncMode = true @@ -185,7 +189,7 @@ TEST_F(CANSilkitTest, InvalidConfigIdentifier) )"; CTestCANSilkit canComObj; - ASSERT_NO_THROW(canComObj.Initialize(ssObjectConfig.c_str())); + ASSERT_NO_THROW(canComObj.Initialize(sObjectInfo)); EXPECT_EQ(canComObj.GetObjectState(), sdv::EObjectState::initialization_failure); ASSERT_NO_THROW(canComObj.Shutdown()); @@ -197,7 +201,8 @@ TEST_F(CANSilkitTest, SendReceiveTest) sdv::app::CAppControl appControl; appControl.Startup(""); - sdv::u8string ssConfig1 = + sdv::SObjectInfo sObjectInfo1{}; + sObjectInfo1.ssConfig = R"( DebugInfo = true SyncMode = true @@ -210,7 +215,8 @@ TEST_F(CANSilkitTest, SendReceiveTest) }, }""" )"; - sdv::u8string ssConfig2 = + sdv::SObjectInfo sObjectInfo2{}; + sObjectInfo2.ssConfig = R"( DebugInfo = true SyncMode = true @@ -228,9 +234,9 @@ TEST_F(CANSilkitTest, SendReceiveTest) CTestCANSilkit canComObj2; MockCANReceiver mockRcv; - canComObj1.Initialize(ssConfig1.c_str()); + canComObj1.Initialize(sObjectInfo1); ASSERT_EQ(canComObj1.GetObjectState(), sdv::EObjectState::initialized); - canComObj2.Initialize(ssConfig2.c_str()); + canComObj2.Initialize(sObjectInfo2); ASSERT_EQ(canComObj2.GetObjectState(), sdv::EObjectState::initialized); ASSERT_NO_THROW(canComObj1.SetOperationMode(sdv::EOperationMode::running)); diff --git a/tests/manual_tests/silkit_can_com_tests/src/can_com_test_silkit.cpp b/tests/manual_tests/silkit_can_com_tests/src/can_com_test_silkit.cpp index 3468b1a..22d35db 100644 --- a/tests/manual_tests/silkit_can_com_tests/src/can_com_test_silkit.cpp +++ b/tests/manual_tests/silkit_can_com_tests/src/can_com_test_silkit.cpp @@ -53,7 +53,7 @@ public: virtual bool OnInitialize() override { m_StopThread = false; - //m_thSend2DatalinkThread = std::thread(&CDummyCANSockets::Send2DatalinkThread, this); + //m_thSend2DatalinkThread = sdv::core::secure_thread(&CDummyCANSockets::Send2DatalinkThread, this); return true; } @@ -116,7 +116,7 @@ public: private: std::atomic_bool m_StopThread = false; - std::thread m_thSend2DatalinkThread; + sdv::core::secure_thread m_thSend2DatalinkThread; mutable std::mutex m_mtxReceivers; std::set m_setReceivers; }; @@ -304,7 +304,7 @@ Class = "CAN_data_link" // Create an object of the CDummyCANSilKit class CDummyCANSilKit canComObj; - canComObj.Initialize(""); + canComObj.Initialize(sdv::SObjectInfo()); sdv::can::ISend* pCanSend = &canComObj; EXPECT_NE(pCanSend, nullptr); @@ -358,7 +358,7 @@ TEST_F(CANCommunicationTest, ExtractAndCompareSignals) // Create a normal object of the CDummyCANSilKit class CDummyCANSilKit canComObj; - canComObj.Initialize(""); + canComObj.Initialize(sdv::SObjectInfo()); sdv::can::ISend* pCanSend = &canComObj; EXPECT_NE(pCanSend, nullptr); diff --git a/tests/manual_tests/silkit_can_com_tests/src/can_reader.cpp b/tests/manual_tests/silkit_can_com_tests/src/can_reader.cpp index 98c84f7..23596aa 100644 --- a/tests/manual_tests/silkit_can_com_tests/src/can_reader.cpp +++ b/tests/manual_tests/silkit_can_com_tests/src/can_reader.cpp @@ -15,7 +15,8 @@ void ReceiveData() sdv::app::CAppControl appControl; appControl.Startup(""); - sdv::u8string config = R"( + sdv::SObjectInfo sObjectInfo{}; + sObjectInfo.ssConfig = R"( DebugInfo = true SyncMode = true SilKitParticipantName = "can_reader" @@ -29,7 +30,7 @@ void ReceiveData() )"; CTestCanSilKit canComObj; - canComObj.Initialize(config.c_str()); + canComObj.Initialize(sObjectInfo); canComObj.SetOperationMode(sdv::EOperationMode::configuring); CanReceiver mockRcv; diff --git a/tests/manual_tests/silkit_can_com_tests/src/can_writer.cpp b/tests/manual_tests/silkit_can_com_tests/src/can_writer.cpp index ca3683d..d6e772b 100644 --- a/tests/manual_tests/silkit_can_com_tests/src/can_writer.cpp +++ b/tests/manual_tests/silkit_can_com_tests/src/can_writer.cpp @@ -15,7 +15,8 @@ int main() sdv::app::CAppControl appControl; appControl.Startup(""); - sdv::u8string config = R"( + sdv::SObjectInfo sObjectInfo{}; + sObjectInfo.ssConfig = R"( DebugInfo = true SyncMode = true SilKitParticipantName = "can_writer" @@ -29,7 +30,7 @@ int main() )"; CTestCanSilKit canComObj; - canComObj.Initialize(config.c_str()); + canComObj.Initialize(sObjectInfo); canComObj.SetOperationMode(sdv::EOperationMode::running); uint8_t msgId = 65; diff --git a/tests/sdv_core_reloc.toml b/tests/sdv_core_reloc.toml index 63ff404..87e35fc 100644 --- a/tests/sdv_core_reloc.toml +++ b/tests/sdv_core_reloc.toml @@ -1,2 +1,16 @@ -# Location of the SDV core library -directory = "../../bin" \ No newline at end of file +# This file is used to overwrite the location of the SDV Core Library regardless of the values of Vehicle API environment variables. +# For test and examples of the SDV Vehicle API Framework, the default location is the binary directory of the target builds. This +# file redirects the location to the binary folder of the SDV Vehicle API Core Framework. +# The environment variables will be updated for the application and all child-applications when using the core loader. The +# locations the environment variables will direct to are: +# - SDV_FRAMEWORK_RUNTIME "../../bin" +# - SDV_COMPONENT_INSTALL "../../bin" +# - SDV_FRAMEWORK_DEV_TOOLS "../../bin" +# - SDV_FRAMEWORK_DEV_INCLUDE "../../../../export" + +[CoreLocation] +Version = 100 +Runtime = "../../bin" +Install = "../../bin" +DevTools = "../../bin" +Include = "../../../../export" \ No newline at end of file diff --git a/tests/unit_tests/app_connect/CMakeLists.txt b/tests/unit_tests/app_settings/CMakeLists.txt similarity index 57% rename from tests/unit_tests/app_connect/CMakeLists.txt rename to tests/unit_tests/app_settings/CMakeLists.txt index 065bea0..2ae426e 100644 --- a/tests/unit_tests/app_connect/CMakeLists.txt +++ b/tests/unit_tests/app_settings/CMakeLists.txt @@ -12,22 +12,22 @@ #******************************************************************************* # Define project -project (UnitTest_App_Connect VERSION 1.0 LANGUAGES CXX) +project (UnitTest_App_Settings VERSION 1.0 LANGUAGES CXX) # Compile the source code -add_executable(UnitTest_App_Connect +add_executable(UnitTest_App_Settings "main.cpp" - "app_connect_local.cpp") -target_link_libraries(UnitTest_App_Connect ${CMAKE_DL_LIBS} GTest::GTest) + "startup_config.cpp" "settings_config.cpp") +target_link_libraries(UnitTest_App_Settings ${CMAKE_DL_LIBS} GTest::GTest) # Add the IDL Compiler unittest -add_test(NAME UnitTest_App_Connect COMMAND UnitTest_App_Connect) +add_test(NAME UnitTest_App_Settings COMMAND UnitTest_App_Settings) # Execute the test -add_custom_command(TARGET UnitTest_App_Connect POST_BUILD - COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake "$" --gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_App_Connect.xml +add_custom_command(TARGET UnitTest_App_Settings POST_BUILD + COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake "$" --gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_App_Settings.xml VERBATIM ) # Build dependencies -add_dependencies(UnitTest_App_Connect dependency_sdv_components) +add_dependencies(UnitTest_App_Settings dependency_sdv_components) diff --git a/tests/unit_tests/app_settings/main.cpp b/tests/unit_tests/app_settings/main.cpp new file mode 100644 index 0000000..6e711a0 --- /dev/null +++ b/tests/unit_tests/app_settings/main.cpp @@ -0,0 +1,41 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#include +#include "../../../global/process_watchdog.h" +#include "../../../global/localmemmgr.h" +#include "../../../sdv_services/core/app_settings.cpp" +#include "../../../sdv_services/core/toml_parser/parser_toml.cpp" +#include "../../../sdv_services/core/toml_parser/parser_node_toml.cpp" +#include "../../../sdv_services/core/toml_parser/lexer_toml.cpp" +#include "../../../sdv_services/core/toml_parser/lexer_toml_token.cpp" +#include "../../../sdv_services/core/toml_parser/character_reader_utf_8.cpp" +#include "../../../sdv_services/core/toml_parser/code_snippet.cpp" +#include "../../../sdv_services/core/toml_parser/parser_node_indexer.cpp" +#include "../../../sdv_services/core/toml_parser/miscellaneous.cpp" + +/** + * @brief Main function + */ +#if defined(_WIN32) && defined(_UNICODE) +extern "C" int wmain(int argc, wchar_t* argv[]) +#else +extern "C" int main(int argc, char* argv[]) +#endif +{ + CProcessWatchdog watchdog; + + CLocalMemMgr memmgr; + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/unit_tests/app_settings/settings_config.cpp b/tests/unit_tests/app_settings/settings_config.cpp new file mode 100644 index 0000000..0951060 --- /dev/null +++ b/tests/unit_tests/app_settings/settings_config.cpp @@ -0,0 +1,537 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#include + +#include "../../../global/exec_dir_helper.h" +#include "../../../sdv_services/core/app_settings.h" +#include "../../../sdv_services/core/toml_parser/miscellaneous.h" +#include "../../../sdv_services/core/toml_parser/parser_toml.h" +#include "../../../sdv_services/core/toml_parser/parser_node_toml.h" +#include + +// Load settings + // Invalid file + // No file + // Config paths + // Listeners + // Connections +// Save settings + // No file + // Config paths + // Listeners + // Connections + +std::filesystem::path GetSettingsFilePath() +{ + // The settings file is located at the exe directory with sub-directory 2000. + return GetExecDirectory() / "2000" / "settings.toml"; +} + +toml_parser::CParser ReadSettingsFile() +{ + std::ifstream fstream(GetSettingsFilePath()); + if (!fstream.is_open()) return {}; + toml_parser::CParser parser(std::string((std::istreambuf_iterator(fstream)), std::istreambuf_iterator())); + fstream.close(); + return parser; +} + +bool WriteSettingsFile(const std::string& rssSettings) +{ + std::filesystem::create_directories(GetSettingsFilePath().remove_filename()); + std::ofstream fstream(GetSettingsFilePath(), std::ios::trunc); + if (!fstream.is_open()) return false; + fstream << rssSettings; + return true; +} + +void DeleteSettingsFile() +{ + if (std::filesystem::exists(GetSettingsFilePath().remove_filename())) + std::filesystem::remove_all(GetSettingsFilePath().remove_filename()); +} + +TEST(AppSettingsTest_SettingsConfig, LoadInvalidConfig) +{ + // Settings for the main application with a specific instance ID + CAppSettings settings; + settings.SetConsoleReporting(CAppSettings::EAppConsoleReporting::silent); + DeleteSettingsFile(); // Needed to prevent settings file parsing during startup. + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Mode = "Main" +Instance = 2000 +InstallDir = )toml" + toml_parser::QuoteText(GetExecDirectory().generic_u8string()))); + + const std::string ssInvalidToml = R"toml([Settings] +Version = hundred # Should be valid number)toml"; + const std::string ssInvalidValue = R"toml([Settings] +Version = "hundred" # Invalid value)toml"; + const std::string ssInvalidVersion = R"toml([Settings] +Version = 99)toml"; + const std::string ssNoVersion = R"toml([Settings])toml"; + + WriteSettingsFile(ssInvalidToml); + EXPECT_FALSE(settings.LoadSettings()); + WriteSettingsFile(ssInvalidValue); + EXPECT_FALSE(settings.LoadSettings()); + WriteSettingsFile(ssInvalidVersion); + EXPECT_FALSE(settings.LoadSettings()); + WriteSettingsFile(ssNoVersion); + EXPECT_FALSE(settings.LoadSettings()); +} + +TEST(AppSettingsTest_SettingsConfig, LoadNoConfig) +{ + // Settings for the main application with a specific instance ID + CAppSettings settings; + settings.SetConsoleReporting(CAppSettings::EAppConsoleReporting::silent); + DeleteSettingsFile(); // Needed to prevent settings file parsing during startup. + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Mode = "Main" +Instance = 2000 +InstallDir = )toml" + toml_parser::QuoteText(GetExecDirectory().generic_u8string()))); + + // Read empty file - this is not an error + EXPECT_TRUE(settings.LoadSettings()); +} + +TEST(AppSettingsTest_SettingsConfig, SaveConfigWrongMode) +{ + // Settings for the main application with a specific instance ID + CAppSettings settings; + settings.SetConsoleReporting(CAppSettings::EAppConsoleReporting::silent); + DeleteSettingsFile(); // Needed to prevent settings file parsing during startup. + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Mode = "Main" +Instance = 2000 +InstallDir = )toml" + toml_parser::QuoteText(GetExecDirectory().generic_u8string()))); + + EXPECT_TRUE(settings.SaveSettings()); + + // Verify for a file + EXPECT_FALSE(std::filesystem::exists(GetSettingsFilePath())); +} + +TEST(AppSettingsTest_SettingsConfig, SaveDefaultConfig) +{ + // Settings for the main application with a specific instance ID + CAppSettings settings; + settings.SetConsoleReporting(CAppSettings::EAppConsoleReporting::silent); + DeleteSettingsFile(); // Needed to prevent settings file parsing during startup. + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Mode = "Maintenance" +Instance = 2000 +InstallDir = )toml" + toml_parser::QuoteText(GetExecDirectory().generic_u8string()))); + + EXPECT_TRUE(settings.SaveSettings()); + + // Verify for a file + EXPECT_TRUE(std::filesystem::exists(GetSettingsFilePath())); + + // Verify settings + auto parser = ReadSettingsFile(); + sdv::toml::CNodeCollection tableSettings(parser.Root().GetNodeDirect("Settings")); + EXPECT_TRUE(tableSettings); + EXPECT_EQ(tableSettings.GetDirect("Version").GetValue(), 100u); + sdv::toml::CNode nodePlatformConfig = tableSettings.GetDirect("PlatformConfig"); + EXPECT_TRUE(nodePlatformConfig); + sdv::toml::CNode nodeVehIfcConfig = tableSettings.GetDirect("VehIfcConfig"); + EXPECT_TRUE(nodeVehIfcConfig); + sdv::toml::CNode nodeVehAbstrConfig = tableSettings.GetDirect("VehAbstrConfig"); + EXPECT_TRUE(nodeVehAbstrConfig); + sdv::toml::CNode nodeAppConfig = tableSettings.GetDirect("AppConfig"); + EXPECT_TRUE(nodeAppConfig); + sdv::toml::CNodeCollection arrayListener = tableSettings.GetDirect("Listener"); + EXPECT_FALSE(arrayListener); + sdv::toml::CNodeCollection arrayConnection = tableSettings.GetDirect("Connection"); + EXPECT_FALSE(arrayConnection); +} + +TEST(AppSettingsTest_SettingsConfig, LoadSettings_SystemConfigs) +{ + // Settings for the main application with a specific instance ID + CAppSettings settings; + settings.SetConsoleReporting(CAppSettings::EAppConsoleReporting::silent); + DeleteSettingsFile(); // Needed to prevent settings file parsing during startup. + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Mode = "Main" +Instance = 2000 +InstallDir = )toml" + toml_parser::QuoteText(GetExecDirectory().generic_u8string()))); + + EXPECT_EQ(settings.GetConfigPath(CAppSettings::EConfigType::platform_config), "platform.toml"); + EXPECT_EQ(settings.GetConfigPath(CAppSettings::EConfigType::vehicle_interface_config), "vehicle_ifc.toml"); + EXPECT_EQ(settings.GetConfigPath(CAppSettings::EConfigType::vehicle_abstraction_config), "vehicle_abstract.toml"); + + WriteSettingsFile(R"toml([Settings] +Version = 100 +PlatformConfig = "abc.toml" +VehIfcConfig = "def.toml" +VehAbstrConfig = "ghi.toml")toml"); + + // Read settings file + EXPECT_TRUE(settings.LoadSettings()); + + EXPECT_EQ(settings.GetConfigPath(CAppSettings::EConfigType::platform_config), "abc.toml"); + EXPECT_EQ(settings.GetConfigPath(CAppSettings::EConfigType::vehicle_interface_config), "def.toml"); + EXPECT_EQ(settings.GetConfigPath(CAppSettings::EConfigType::vehicle_abstraction_config), "ghi.toml"); +} + +TEST(AppSettingsTest_SettingsConfig, SaveSettings_SystemConfigs) +{ + // Settings for the main application with a specific instance ID + CAppSettings settings; + settings.SetConsoleReporting(CAppSettings::EAppConsoleReporting::silent); + DeleteSettingsFile(); // Needed to prevent settings file parsing during startup. + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Mode = "Maintenance" +Instance = 2000 +InstallDir = )toml" + toml_parser::QuoteText(GetExecDirectory().generic_u8string()))); + + EXPECT_EQ(settings.GetConfigPath(CAppSettings::EConfigType::platform_config), "platform.toml"); + EXPECT_EQ(settings.GetConfigPath(CAppSettings::EConfigType::vehicle_interface_config), "vehicle_ifc.toml"); + EXPECT_EQ(settings.GetConfigPath(CAppSettings::EConfigType::vehicle_abstraction_config), "vehicle_abstract.toml"); + settings.DisableConfig(CAppSettings::EConfigType::platform_config); + settings.DisableConfig(CAppSettings::EConfigType::vehicle_interface_config); + settings.DisableConfig(CAppSettings::EConfigType::vehicle_abstraction_config); + EXPECT_EQ(settings.GetConfigPath(CAppSettings::EConfigType::platform_config), ""); + EXPECT_EQ(settings.GetConfigPath(CAppSettings::EConfigType::vehicle_interface_config), ""); + EXPECT_EQ(settings.GetConfigPath(CAppSettings::EConfigType::vehicle_abstraction_config), ""); + + EXPECT_TRUE(settings.SaveSettings()); + + // Verify settings + auto parser = ReadSettingsFile(); + sdv::toml::CNodeCollection tableSettings(parser.Root().GetNodeDirect("Settings")); + EXPECT_TRUE(tableSettings); + EXPECT_TRUE(tableSettings.GetDirect("PlatformConfig").GetValueAsPath().empty()); + EXPECT_TRUE(tableSettings.GetDirect("VehIfcConfig").GetValueAsPath().empty()); + EXPECT_TRUE(tableSettings.GetDirect("VehAbstrConfig").GetValueAsPath().empty()); + + settings.EnableConfig(CAppSettings::EConfigType::platform_config); + settings.EnableConfig(CAppSettings::EConfigType::vehicle_interface_config); + settings.EnableConfig(CAppSettings::EConfigType::vehicle_abstraction_config); + EXPECT_EQ(settings.GetConfigPath(CAppSettings::EConfigType::platform_config), "platform.toml"); + EXPECT_EQ(settings.GetConfigPath(CAppSettings::EConfigType::vehicle_interface_config), "vehicle_ifc.toml"); + EXPECT_EQ(settings.GetConfigPath(CAppSettings::EConfigType::vehicle_abstraction_config), "vehicle_abstract.toml"); + + EXPECT_TRUE(settings.SaveSettings()); + + // Verify settings + auto parser2 = ReadSettingsFile(); + sdv::toml::CNodeCollection tableSettings2(parser2.Root().GetNodeDirect("Settings")); + EXPECT_TRUE(tableSettings2); + EXPECT_EQ(tableSettings2.GetDirect("PlatformConfig").GetValueAsPath(), "platform.toml"); + EXPECT_EQ(tableSettings2.GetDirect("VehIfcConfig").GetValueAsPath(), "vehicle_ifc.toml"); + EXPECT_EQ(tableSettings2.GetDirect("VehAbstrConfig").GetValueAsPath(), "vehicle_abstract.toml"); +} + +TEST(AppSettingsTest_SettingsConfig, LoadSettings_UserConfig) +{ + // Settings for the main application with a specific instance ID + CAppSettings settings; + settings.SetConsoleReporting(CAppSettings::EAppConsoleReporting::silent); + DeleteSettingsFile(); // Needed to prevent settings file parsing during startup. + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Mode = "Main" +Instance = 2000 +InstallDir = )toml" + toml_parser::QuoteText(GetExecDirectory().generic_u8string()))); + + EXPECT_EQ(settings.GetConfigPath(CAppSettings::EConfigType::user_config), "app_config.toml"); + + WriteSettingsFile(R"toml([Settings] +Version = 100 +AppConfig = "xyz.toml")toml"); + + // Read settings file + EXPECT_TRUE(settings.LoadSettings()); + + EXPECT_EQ(settings.GetConfigPath(CAppSettings::EConfigType::user_config), "xyz.toml"); + + settings.Reset(); + + // For all configurations except main, isolated and maintenance, the user config is supplied through the app config. + DeleteSettingsFile(); // Needed to prevent settings file parsing during startup. + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Mode = "Standalone" +InstallDir = )toml" + toml_parser::QuoteText(GetExecDirectory().generic_u8string()) + R"toml( +Config = "klm.toml")toml")); + + EXPECT_EQ(settings.GetUserConfigPath(), "klm.toml"); +} + +TEST(AppSettingsTest_SettingsConfig, SaveSettings_UserConfig) +{ + // Settings for the main application with a specific instance ID + CAppSettings settings; + settings.SetConsoleReporting(CAppSettings::EAppConsoleReporting::silent); + DeleteSettingsFile(); // Needed to prevent settings file parsing during startup. + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Mode = "Maintenance" +Instance = 2000 +InstallDir = )toml" + toml_parser::QuoteText(GetExecDirectory().generic_u8string()))); + + EXPECT_EQ(settings.GetConfigPath(CAppSettings::EConfigType::user_config), "app_config.toml"); + settings.DisableConfig(CAppSettings::EConfigType::user_config); + EXPECT_EQ(settings.GetConfigPath(CAppSettings::EConfigType::user_config), ""); + + EXPECT_TRUE(settings.SaveSettings()); + + // Verify settings + auto parser = ReadSettingsFile(); + sdv::toml::CNodeCollection tableSettings(parser.Root().GetNodeDirect("Settings")); + EXPECT_TRUE(tableSettings); + EXPECT_TRUE(tableSettings.GetDirect("AppConfig").GetValueAsPath().empty()); + + settings.EnableConfig(CAppSettings::EConfigType::user_config); + EXPECT_EQ(settings.GetConfigPath(CAppSettings::EConfigType::user_config), "app_config.toml"); + + EXPECT_TRUE(settings.SaveSettings()); + + // Verify settings + auto parser2 = ReadSettingsFile(); + sdv::toml::CNodeCollection tableSettings2(parser2.Root().GetNodeDirect("Settings")); + EXPECT_TRUE(tableSettings2); + EXPECT_EQ(tableSettings2.GetDirect("AppConfig").GetValueAsPath(), "app_config.toml"); +} + +TEST(AppSettingsTest_SettingsConfig, LoadSettings_Listeners) +{ + // Settings for the main application with a specific instance ID + CAppSettings settings; + settings.SetConsoleReporting(CAppSettings::EAppConsoleReporting::silent); + DeleteSettingsFile(); // Needed to prevent settings file parsing during startup. + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Mode = "Main" +Instance = 2000 +InstallDir = )toml" + toml_parser::QuoteText(GetExecDirectory().generic_u8string()))); + + // Default listener shoule be available. + EXPECT_EQ(settings.GetListeners().size(), 1u); + + WriteSettingsFile(R"toml([Settings] +Version = 100 + +[[Settings.Listener]] +Name = "MyListener1" +[Settings.Listener.Provider] +Name = "MyListenerProvider1" +[Settings.Listener.IpcChannel] +abc = "def" + +[[Settings.Listener]] +Name = "MyListener2" +[Settings.Listener.Provider] +Name = "MyListenerProvider2" +[Settings.Listener.IpcChannel] +xyz = 1234 +)toml"); + + // Read settings file - note: the listeners are part of a listener map. The order is not fixed. + // There will be an additional default listener. + EXPECT_TRUE(settings.LoadSettings()); + auto seqListeners = settings.GetListeners(); + ASSERT_EQ(seqListeners.size(), 3u); + EXPECT_TRUE(seqListeners[0] == "MyListener1" || seqListeners[1] == "MyListener1" || seqListeners[2] == "MyListener1"); + EXPECT_TRUE(seqListeners[0] == "MyListener2" || seqListeners[1] == "MyListener2" || seqListeners[2] == "MyListener2"); + EXPECT_TRUE(seqListeners[0] == "Default" || seqListeners[1] == "Default" || seqListeners[2] == "Default"); + EXPECT_TRUE(settings.GetListenerConfig("MyListener0").empty()); + EXPECT_TRUE(toml_parser::CompareEqual(settings.GetListenerConfig("MyListener1"), R"toml([Provider] +Name = "MyListenerProvider1" + +[IpcChannel] +abc = "def" +)toml")); + EXPECT_TRUE(toml_parser::CompareEqual(settings.GetListenerConfig("MyListener2"), R"toml([Provider] +Name = "MyListenerProvider2" + +[IpcChannel] +xyz = 1234 +)toml")); +} + +TEST(AppSettingsTest_SettingsConfig, SaveSettings_Listeners) +{ + // Settings for the main application with a specific instance ID + CAppSettings settings; + settings.SetConsoleReporting(CAppSettings::EAppConsoleReporting::silent); + DeleteSettingsFile(); // Needed to prevent settings file parsing during startup. + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Mode = "Maintenance" +Instance = 2000 +InstallDir = )toml" + toml_parser::QuoteText(GetExecDirectory().generic_u8string()))); + + EXPECT_EQ(settings.GetListeners().size(), 1u); + + EXPECT_TRUE(settings.AddListenerConfig("MyListener2", R"toml([Provider] +Name = "MyListenerProvider2" +[IpcChannel] +abc = "def")toml")); + EXPECT_EQ(settings.GetListeners().size(), 2u); + + EXPECT_TRUE(settings.AddListenerConfig("MyListener1", R"toml([Provider] +Name = "MyListenerProvider1" +[IpcChannel] +xyz = 1234)toml")); + EXPECT_EQ(settings.GetListeners().size(), 3u); + + EXPECT_TRUE(settings.SaveSettings()); + + // Verify settings + auto parser = ReadSettingsFile(); + sdv::toml::CNodeCollection tableSettings(parser.Root().GetNodeDirect("Settings")); + [[maybe_unused]] std::string ss = parser.GenerateTOML(); + EXPECT_TRUE(tableSettings); + sdv::toml::CNodeCollection tableListener = tableSettings.GetDirect("Listener[0]"); + if (tableListener.GetDirect("Name").GetValueAsString() != "MyListener1") + tableListener = tableSettings.GetDirect("Listener[1]"); + EXPECT_TRUE(tableListener); + EXPECT_EQ(tableListener.GetDirect("Name").GetValueAsString(), "MyListener1"); + EXPECT_EQ(tableListener.GetDirect("Provider.Name").GetValueAsString(), "MyListenerProvider1"); + EXPECT_EQ(tableListener.GetDirect("IpcChannel.xyz").GetValue(), 1234); + tableListener = tableSettings.GetDirect("Listener[0]"); + if (tableListener.GetDirect("Name").GetValueAsString() != "MyListener2") + tableListener = tableSettings.GetDirect("Listener[1]"); + EXPECT_TRUE(tableListener); + EXPECT_EQ(tableListener.GetDirect("Name").GetValueAsString(), "MyListener2"); + EXPECT_EQ(tableListener.GetDirect("Provider.Name").GetValueAsString(), "MyListenerProvider2"); + EXPECT_EQ(tableListener.GetDirect("IpcChannel.abc").GetValueAsString(), "def"); + + // Remove one listener + settings.RemoveListenerConfig("MyListener1"); + EXPECT_EQ(settings.GetListeners().size(), 2u); + EXPECT_TRUE(settings.SaveSettings()); + + // Verify settings + auto parser2 = ReadSettingsFile(); + sdv::toml::CNodeCollection tableSettings2(parser2.Root().GetNodeDirect("Settings")); + EXPECT_TRUE(tableSettings2); + tableListener = tableSettings2.GetDirect("Listener[0]"); + EXPECT_TRUE(tableListener); + EXPECT_EQ(tableListener.GetDirect("Name").GetValueAsString(), "MyListener2"); + EXPECT_EQ(tableListener.GetDirect("Provider.Name").GetValueAsString(), "MyListenerProvider2"); + EXPECT_EQ(tableListener.GetDirect("IpcChannel.abc").GetValueAsString(), "def"); +} + +TEST(AppSettingsTest_SettingsConfig, LoadSettings_Connections) +{ + // Settings for the main application with a specific instance ID + CAppSettings settings; + settings.SetConsoleReporting(CAppSettings::EAppConsoleReporting::silent); + DeleteSettingsFile(); // Needed to prevent settings file parsing during startup. + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Mode = "Main" +Instance = 2000 +InstallDir = )toml" + toml_parser::QuoteText(GetExecDirectory().generic_u8string()))); + + // Default connection only + EXPECT_EQ(settings.GetConnections().size(), 1); + + WriteSettingsFile(R"toml([Settings] +Version = 100 + +[[Settings.Connection]] +Name = "MyConnection1" +[Settings.Connection.Provider] +Name = "MyConnectionProvider1" +[Settings.Connection.IpcChannel] +abc = "def" + +[[Settings.Connection]] +Name = "MyConnection2" +[Settings.Connection.Provider] +Name = "MyConnectionProvider2" +[Settings.Connection.IpcChannel] +xyz = 1234 +)toml"); + + // Read settings file - note: the order of connections is preserved in the settings file. + // There will be an additional default connection. + EXPECT_TRUE(settings.LoadSettings()); + auto seqConnections = settings.GetConnections(); + ASSERT_EQ(seqConnections.size(), 3u); + EXPECT_EQ(seqConnections[0], "Default"); + EXPECT_EQ(seqConnections[1], "MyConnection1"); + EXPECT_EQ(seqConnections[2], "MyConnection2"); + EXPECT_TRUE(settings.GetConnectionConfig("MyConnection0").empty()); + EXPECT_TRUE(toml_parser::CompareEqual(settings.GetConnectionConfig("MyConnection1"), R"toml([Provider] +Name = "MyConnectionProvider1" + +[IpcChannel] +abc = "def" +)toml")); + EXPECT_TRUE(toml_parser::CompareEqual(settings.GetConnectionConfig("MyConnection2"), R"toml([Provider] +Name = "MyConnectionProvider2" + +[IpcChannel] +xyz = 1234 +)toml")); +} + +TEST(AppSettingsTest_SettingsConfig, SaveSettings_Connections) +{ + // Settings for the main application with a specific instance ID + CAppSettings settings; + settings.SetConsoleReporting(CAppSettings::EAppConsoleReporting::silent); + DeleteSettingsFile(); // Needed to prevent settings file parsing during startup. + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Mode = "Maintenance" +Instance = 2000 +InstallDir = )toml" + toml_parser::QuoteText(GetExecDirectory().generic_u8string()))); + + EXPECT_EQ(settings.GetConnections().size(), 1u); + + EXPECT_TRUE(settings.AddConnectionConfig("MyConnection2", R"toml([Provider] +Name = "MyConnectionProvider2" +[IpcChannel] +abc = "def")toml")); + EXPECT_EQ(settings.GetConnections().size(), 2u); + + EXPECT_TRUE(settings.AddConnectionConfig("MyConnection1", R"toml([Provider] +Name = "MyConnectionProvider1" +[IpcChannel] +xyz = 1234)toml")); + EXPECT_EQ(settings.GetConnections().size(), 3u); + + EXPECT_TRUE(settings.SaveSettings()); + + // Verify settings + auto parser = ReadSettingsFile(); + sdv::toml::CNodeCollection tableSettings(parser.Root().GetNodeDirect("Settings")); + EXPECT_TRUE(tableSettings); + sdv::toml::CNodeCollection tableConnection = tableSettings.GetDirect("Connection[0]"); + EXPECT_TRUE(tableConnection); + EXPECT_EQ(tableConnection.GetDirect("Name").GetValueAsString(), "MyConnection2"); + EXPECT_EQ(tableConnection.GetDirect("Provider.Name").GetValueAsString(), "MyConnectionProvider2"); + EXPECT_EQ(tableConnection.GetDirect("IpcChannel.abc").GetValueAsString(), "def"); + tableConnection = tableSettings.GetDirect("Connection[1]"); + EXPECT_TRUE(tableConnection); + EXPECT_EQ(tableConnection.GetDirect("Name").GetValueAsString(), "MyConnection1"); + EXPECT_EQ(tableConnection.GetDirect("Provider.Name").GetValueAsString(), "MyConnectionProvider1"); + EXPECT_EQ(tableConnection.GetDirect("IpcChannel.xyz").GetValue(), 1234); + + // Remove one listener + settings.RemoveConnectionConfig("MyConnection1"); + EXPECT_EQ(settings.GetConnections().size(), 2u); + EXPECT_TRUE(settings.SaveSettings()); + + // Verify settings + auto parser2 = ReadSettingsFile(); + sdv::toml::CNodeCollection tableSettings2(parser2.Root().GetNodeDirect("Settings")); + EXPECT_TRUE(tableSettings2); + tableConnection = tableSettings2.GetDirect("Connection[0]"); + EXPECT_TRUE(tableConnection); + EXPECT_EQ(tableConnection.GetDirect("Name").GetValueAsString(), "MyConnection2"); + EXPECT_EQ(tableConnection.GetDirect("Provider.Name").GetValueAsString(), "MyConnectionProvider2"); + EXPECT_EQ(tableConnection.GetDirect("IpcChannel.abc").GetValueAsString(), "def"); +} diff --git a/tests/unit_tests/app_settings/startup_config.cpp b/tests/unit_tests/app_settings/startup_config.cpp new file mode 100644 index 0000000..7182d76 --- /dev/null +++ b/tests/unit_tests/app_settings/startup_config.cpp @@ -0,0 +1,341 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#include +#include "../../../sdv_services/core/app_settings.h" +#include "../../../global/exec_dir_helper.h" +#include "../../../sdv_services/core/toml_parser/miscellaneous.h" + +// Load startup config + // Invalid config + // Empty config (default config) + // Application types + // Instance ID + // Log handler + // Console output + // Config path + +TEST(AppSettingsTest_StartupConfig, InvalidConfig) +{ + const std::string ssInvalidToml = R"toml([Application] +Mode = Standalone # Missing quotes)toml"; + const std::string ssInvalidValue = R"toml([Application] +Mode = "Supadupa" # Invalid value)toml"; + const std::string ssAdditionalValues = R"toml([Application] +Mode = "Standalone" +SpecialAction = "Fly to the moon" # Additional value)toml"; + + CAppSettings settings; + settings.SetConsoleReporting(CAppSettings::EAppConsoleReporting::silent); + EXPECT_FALSE(settings.ProcessAppStartupConfig(ssInvalidToml)); + EXPECT_FALSE(settings.ProcessAppStartupConfig(ssInvalidValue)); + EXPECT_TRUE(settings.ProcessAppStartupConfig(ssAdditionalValues)); +} + +TEST(AppSettingsTest_StartupConfig, DefaultConfig) +{ + const std::string ssEmptyToml; + + CAppSettings settings; + EXPECT_EQ(settings.GetInstanceID(), 0u); + EXPECT_TRUE(settings.ProcessAppStartupConfig(ssEmptyToml)); + EXPECT_TRUE(settings.IsStandaloneApplication()); + EXPECT_EQ(settings.GetLoggerSeverityFilter(), sdv::core::ELogSeverity::info); + EXPECT_EQ(settings.GetConsoleSeverityFilter(), sdv::core::ELogSeverity::error); + EXPECT_EQ(settings.GetConsoleReporting(), CAppSettings::EAppConsoleReporting::normal); + EXPECT_EQ(settings.GetInstanceID(), 1000u); +} + +TEST(AppSettingsTest_StartupConfig, ApplicationTypes) +{ + CAppSettings settings; + settings.SetConsoleReporting(CAppSettings::EAppConsoleReporting::silent); + EXPECT_EQ(settings.GetContextType(), sdv::app::EAppContext::no_context); + EXPECT_TRUE(settings.ProcessAppStartupConfig("")); + EXPECT_EQ(settings.GetContextType(), sdv::app::EAppContext::standalone); + EXPECT_TRUE(settings.IsStandaloneApplication()); + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Mode = "External")toml")); + EXPECT_EQ(settings.GetContextType(), sdv::app::EAppContext::external); + EXPECT_TRUE(settings.IsExternalApplication()); + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Mode = "Standalone")toml")); + EXPECT_EQ(settings.GetContextType(), sdv::app::EAppContext::standalone); + EXPECT_TRUE(settings.IsStandaloneApplication()); + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Mode = "Isolated")toml")); + EXPECT_EQ(settings.GetContextType(), sdv::app::EAppContext::isolated); + EXPECT_TRUE(settings.IsIsolatedApplication()); + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Mode = "Main")toml")); + EXPECT_EQ(settings.GetContextType(), sdv::app::EAppContext::main); + EXPECT_TRUE(settings.IsMainApplication()); + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Mode = "Essential")toml")); + EXPECT_EQ(settings.GetContextType(), sdv::app::EAppContext::essential); + EXPECT_TRUE(settings.IsEssentialApplication()); + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Mode = "Maintenance")toml")); + EXPECT_EQ(settings.GetContextType(), sdv::app::EAppContext::maintenance); + EXPECT_TRUE(settings.IsMaintenanceApplication()); +} + +TEST(AppSettingsTest_StartupConfig, InstanceID) +{ + CAppSettings settings; + settings.SetConsoleReporting(CAppSettings::EAppConsoleReporting::silent); + EXPECT_EQ(settings.GetInstanceID(), 0u); + + // Change type to main; no instance ID yet + settings.SetContextType(sdv::app::EAppContext::main); + EXPECT_EQ(settings.GetInstanceID(), 0u); + + // Set instance ID, but no type + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Instance = 1234)toml")); + EXPECT_EQ(settings.GetInstanceID(), 1234u); + + // Change types, only main, isolated and maintenance should provide the instance ID. + settings.SetContextType(sdv::app::EAppContext::standalone); + EXPECT_EQ(settings.GetInstanceID(), 1234u); + settings.SetContextType(sdv::app::EAppContext::external); + EXPECT_EQ(settings.GetInstanceID(), 1234u); + settings.SetContextType(sdv::app::EAppContext::isolated); + EXPECT_EQ(settings.GetInstanceID(), 1234u); + settings.SetContextType(sdv::app::EAppContext::main); + EXPECT_EQ(settings.GetInstanceID(), 1234u); + settings.SetContextType(sdv::app::EAppContext::essential); + EXPECT_EQ(settings.GetInstanceID(), 1234u); + settings.SetContextType(sdv::app::EAppContext::maintenance); + EXPECT_EQ(settings.GetInstanceID(), 1234u); + settings.Reset(); + EXPECT_EQ(settings.GetInstanceID(), 0u); +} + +TEST(AppSettingsTest_StartupConfig, LogHandler) +{ + CAppSettings settings; + settings.SetConsoleReporting(CAppSettings::EAppConsoleReporting::silent); + + // No config + EXPECT_TRUE(settings.GetLoggerClass().empty()); + EXPECT_TRUE(settings.GetLoggerModulePath().empty()); + EXPECT_TRUE(settings.GetLoggerProgramTag().empty()); + EXPECT_EQ(settings.GetLoggerSeverityFilter(), sdv::core::ELogSeverity::info); + EXPECT_EQ(settings.GetConsoleSeverityFilter(), sdv::core::ELogSeverity::error); + + // Default config + EXPECT_TRUE(settings.ProcessAppStartupConfig("")); + EXPECT_EQ(settings.GetLoggerClass(), "DefaultLoggerService"); + EXPECT_TRUE(settings.GetLoggerModulePath().empty()); + EXPECT_TRUE(settings.GetLoggerProgramTag().empty()); + EXPECT_EQ(settings.GetLoggerSeverityFilter(), sdv::core::ELogSeverity::info); + EXPECT_EQ(settings.GetConsoleSeverityFilter(), sdv::core::ELogSeverity::error); + + // Default logger when main or isolated + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Mode = "Main")toml")); + EXPECT_EQ(settings.GetLoggerClass(), "DefaultLoggerService"); + EXPECT_TRUE(settings.GetLoggerModulePath().empty()); + EXPECT_TRUE(settings.GetLoggerProgramTag().empty()); + EXPECT_EQ(settings.GetLoggerSeverityFilter(), sdv::core::ELogSeverity::info); + EXPECT_EQ(settings.GetConsoleSeverityFilter(), sdv::core::ELogSeverity::info); + + // Default logger when main or isolated + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Mode = "Isolated")toml")); + EXPECT_EQ(settings.GetLoggerClass(), "DefaultLoggerService"); + EXPECT_TRUE(settings.GetLoggerModulePath().empty()); + EXPECT_TRUE(settings.GetLoggerProgramTag().empty()); + EXPECT_EQ(settings.GetLoggerSeverityFilter(), sdv::core::ELogSeverity::info); + EXPECT_EQ(settings.GetConsoleSeverityFilter(), sdv::core::ELogSeverity::info); + + // Default logger for all others + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Mode = "Standalone")toml")); + EXPECT_EQ(settings.GetLoggerClass(), "DefaultLoggerService"); + EXPECT_TRUE(settings.GetLoggerModulePath().empty()); + EXPECT_TRUE(settings.GetLoggerProgramTag().empty()); + EXPECT_EQ(settings.GetLoggerSeverityFilter(), sdv::core::ELogSeverity::info); + EXPECT_EQ(settings.GetConsoleSeverityFilter(), sdv::core::ELogSeverity::error); + + // Set severity level for logger + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([LogHandler] +Filter = "Trace")toml")); + EXPECT_EQ(settings.GetLoggerSeverityFilter(), sdv::core::ELogSeverity::trace); + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([LogHandler] +Filter = "Debug")toml")); + EXPECT_EQ(settings.GetLoggerSeverityFilter(), sdv::core::ELogSeverity::debug); + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([LogHandler] +Filter = "Info")toml")); + EXPECT_EQ(settings.GetLoggerSeverityFilter(), sdv::core::ELogSeverity::info); + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([LogHandler] +Filter = "Warning")toml")); + EXPECT_EQ(settings.GetLoggerSeverityFilter(), sdv::core::ELogSeverity::warning); + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([LogHandler] +Filter = "Error")toml")); + EXPECT_EQ(settings.GetLoggerSeverityFilter(), sdv::core::ELogSeverity::error); + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([LogHandler] +Filter = "Fatal")toml")); + EXPECT_EQ(settings.GetLoggerSeverityFilter(), sdv::core::ELogSeverity::fatal); + EXPECT_FALSE(settings.ProcessAppStartupConfig(R"toml([LogHandler] +Filter = "Wrong")toml")); + EXPECT_EQ(settings.GetLoggerSeverityFilter(), sdv::core::ELogSeverity::fatal); + + // Set severity level for console + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([LogHandler] +ViewFilter = "Trace")toml")); + EXPECT_EQ(settings.GetConsoleSeverityFilter(), sdv::core::ELogSeverity::trace); + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([LogHandler] +ViewFilter = "Debug")toml")); + EXPECT_EQ(settings.GetConsoleSeverityFilter(), sdv::core::ELogSeverity::debug); + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([LogHandler] +ViewFilter = "Info")toml")); + EXPECT_EQ(settings.GetConsoleSeverityFilter(), sdv::core::ELogSeverity::info); + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([LogHandler] +ViewFilter = "Warning")toml")); + EXPECT_EQ(settings.GetConsoleSeverityFilter(), sdv::core::ELogSeverity::warning); + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([LogHandler] +ViewFilter = "Error")toml")); + EXPECT_EQ(settings.GetConsoleSeverityFilter(), sdv::core::ELogSeverity::error); + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([LogHandler] +ViewFilter = "Fatal")toml")); + EXPECT_EQ(settings.GetConsoleSeverityFilter(), sdv::core::ELogSeverity::fatal); + EXPECT_FALSE(settings.ProcessAppStartupConfig(R"toml([LogHandler] +ViewFilter = "Wrong")toml")); + EXPECT_EQ(settings.GetConsoleSeverityFilter(), sdv::core::ELogSeverity::fatal); + + // Explicit logger + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([LogHandler] +Class = "logger_class" +Path = "logger.xyz" +Tag = "logger_tag" +Filter = "Trace" +ViewFilter = "Warning" +UnkownValue = "hi")toml")); + EXPECT_EQ(settings.GetLoggerClass(), "logger_class"); + EXPECT_EQ(settings.GetLoggerModulePath(), "logger.xyz"); + EXPECT_EQ(settings.GetLoggerProgramTag(), "logger_tag"); + EXPECT_EQ(settings.GetLoggerSeverityFilter(), sdv::core::ELogSeverity::trace); + EXPECT_EQ(settings.GetConsoleSeverityFilter(), sdv::core::ELogSeverity::warning); +} + +TEST(AppSettingsTest_StartupConfig, ConnectionRetries) +{ + CAppSettings settings; + settings.SetConsoleReporting(CAppSettings::EAppConsoleReporting::silent); + + // Default is 5 retries + EXPECT_EQ(settings.GetConnectRetries(), 5u); + EXPECT_TRUE(settings.ProcessAppStartupConfig("")); + EXPECT_EQ(settings.GetConnectRetries(), 5u); + + // Set 0 retries - this is not an error and minimizes to 3 + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Connections] +Retries = 0)toml")); + EXPECT_EQ(settings.GetConnectRetries(), 3u); + + // Set 10 retries + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Connections] +Retries = 10)toml")); + EXPECT_EQ(settings.GetConnectRetries(), 10u); + + // Set 100 retries - this is not an error and maximizes to 30 + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Connections] +Retries = 100)toml")); + EXPECT_EQ(settings.GetConnectRetries(), 30u); +} + +TEST(AppSettingsTest_StartupConfig, InstallDir) +{ + CAppSettings settings; + settings.SetConsoleReporting(CAppSettings::EAppConsoleReporting::silent); + + // Empty install directory + EXPECT_TRUE(settings.GetRootDir().empty()); + EXPECT_TRUE(settings.GetInstallDir().empty()); + + // Install directory has significance with main, isolated and maintenance apps only. + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +InstallDir = )toml" + toml_parser::QuoteText(GetExecDirectory().generic_u8string()))); + EXPECT_TRUE(settings.GetRootDir().empty()); + EXPECT_TRUE(settings.GetInstallDir().empty()); + + // Use main and a specific instance ID + std::filesystem::remove_all(GetExecDirectory() / "2000"); // Needed to prevent settings file parsing. + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Mode = "Main" +Instance = 2000 +InstallDir = )toml" + toml_parser::QuoteText(GetExecDirectory().generic_u8string()))); + EXPECT_EQ(settings.GetRootDir(), GetExecDirectory()); + EXPECT_EQ(settings.GetInstallDir(), GetExecDirectory() / "2000"); + + // Use isolated and a specific instance ID + std::filesystem::remove_all(GetExecDirectory() / "2001"); // Needed to prevent settings file parsing. + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Mode = "Isolated" +Instance = 2001 +InstallDir = )toml" + toml_parser::QuoteText(GetExecDirectory().generic_u8string()))); + EXPECT_EQ(settings.GetRootDir(), GetExecDirectory()); + EXPECT_EQ(settings.GetInstallDir(), GetExecDirectory() / "2001"); + + // Use maintenance and a specific instance ID + std::filesystem::remove_all(GetExecDirectory() / "2002"); // Needed to prevent settings file parsing. + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Mode = "Maintenance" +Instance = 2002 +InstallDir = )toml" + toml_parser::QuoteText(GetExecDirectory().generic_u8string()))); + EXPECT_EQ(settings.GetRootDir(), GetExecDirectory()); + EXPECT_EQ(settings.GetInstallDir(), GetExecDirectory() / "2002"); + + // Relative path + std::filesystem::remove_all(GetExecDirectory() / "2003"); // Needed to prevent settings file parsing. + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Mode = "Maintenance" +Instance = 2003 +InstallDir = "..")toml")); + EXPECT_EQ(settings.GetRootDir(), GetExecDirectory() / ".."); + EXPECT_EQ(settings.GetInstallDir(), GetExecDirectory() / ".." / "2003"); +} + +TEST(AppSettingsTest_StartupConfig, CustomConfigFile) +{ + CAppSettings settings; + settings.SetConsoleReporting(CAppSettings::EAppConsoleReporting::silent); + + // Default empty + EXPECT_TRUE(settings.GetUserConfigPath().empty()); + + // Application config path has no significance with main, isolated and maintenance apps only. + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Config = "abc.def")toml")); + EXPECT_EQ(settings.GetUserConfigPath(), "abc.def"); + + // Use main + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Mode = "Main" +Config = "def.hij")toml")); + EXPECT_EQ(settings.GetUserConfigPath(), "abc.def"); + + // Use isolated + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Mode = "Isolated" +Config = "hij.klm")toml")); + EXPECT_EQ(settings.GetUserConfigPath(), "abc.def"); + + // Use maintenance + EXPECT_TRUE(settings.ProcessAppStartupConfig(R"toml([Application] +Mode = "Maintenance" +Config = "klm.nop")toml")); + EXPECT_EQ(settings.GetUserConfigPath(), "abc.def"); +} \ No newline at end of file diff --git a/tests/unit_tests/core_loader/core_loader_test.cpp b/tests/unit_tests/core_loader/core_loader_test.cpp index 093fc54..982b677 100644 --- a/tests/unit_tests/core_loader/core_loader_test.cpp +++ b/tests/unit_tests/core_loader/core_loader_test.cpp @@ -283,7 +283,7 @@ TEST(CoreLoaderTest, EnvVarLoading) EXPECT_EQ(executor.Execute(), 1); } -TEST(CoreLoaderTest, CfgFileLoadingRel) +TEST(CoreLoaderTest, CoreRelocFileLoadingRel) { CTestExecute executor; @@ -291,14 +291,17 @@ TEST(CoreLoaderTest, CfgFileLoadingRel) fstream << R"cfg(# Some bogus information unknown_var = 2 +[CoreLocation] +Version = 100 + # Core library path -directory = "../../../bin")cfg"; +Runtime = "../../../bin")cfg"; fstream.close(); EXPECT_EQ(executor.Execute(), 1); } -TEST(CoreLoaderTest, CfgFileLoadingAbs) +TEST(CoreLoaderTest, CoreRelocFileLoadingAbs) { CTestExecute executor; @@ -306,15 +309,18 @@ TEST(CoreLoaderTest, CfgFileLoadingAbs) fstream << R"cfg(# Some bogus information unknown_var = 2 +[CoreLocation] +Version = 100 + # Core library path -directory = ")cfg" << (executor.GetExeDir() / "../../../bin").lexically_normal().generic_string() << "\""; +Runtime = ")cfg" << (executor.GetExeDir() / "../../../bin").lexically_normal().generic_string() << "\""; fstream.close(); EXPECT_EQ(executor.Execute(), 1); } #ifdef _WIN32 -TEST(CoreLoaderTest, CfgFileLoadingRelWin) +TEST(CoreLoaderTest, CoreRelocFileLoadingRelWin) { CTestExecute executor; @@ -322,14 +328,17 @@ TEST(CoreLoaderTest, CfgFileLoadingRelWin) fstream << R"cfg(# Some bogus information unknown_var = 2 +[CoreLocation] +Version = 100 + # Core library path -directory = "..\\..\\..\\bin")cfg"; +Runtime = "..\\..\\..\\bin")cfg"; fstream.close(); EXPECT_EQ(executor.Execute(), 1); } -TEST(CoreLoaderTest, CfgFileLoadingAbsWin) +TEST(CoreLoaderTest, CoreRelocFileLoadingAbsWin) { CTestExecute executor; @@ -352,8 +361,11 @@ TEST(CoreLoaderTest, CfgFileLoadingAbsWin) fstream << R"cfg(# Some bogus information unknown_var = 2 +[CoreLocation] +Version = 100 + # Core library path -directory = ")cfg" << ssDir << "\""; +Runtime = ")cfg" << ssDir << "\""; fstream.close(); EXPECT_EQ(executor.Execute(), 1); diff --git a/tests/unit_tests/dbc_parser/dbc_parser_test.cpp b/tests/unit_tests/dbc_parser/dbc_parser_test.cpp index 752e8bc..69c8ba7 100644 --- a/tests/unit_tests/dbc_parser/dbc_parser_test.cpp +++ b/tests/unit_tests/dbc_parser/dbc_parser_test.cpp @@ -156,7 +156,7 @@ TEST_F(CDbcParserTest, SourceContent) TEST_F(CDbcParserTest, SourceLineColumn) { - std::string ssSource = R"code(VERSION "" + std::string ssSource = R"dbc(VERSION "" NS_ : @@ -169,7 +169,7 @@ NS_ : CAT_ FILTER BA_DEF_DEF_ - EV_DATA_)code"; + EV_DATA_)dbc"; dbc::CDbcSource src(ssSource); EXPECT_EQ(src.CalcLine(), 1); @@ -370,10 +370,10 @@ TEST_F(CDbcParserTest, NewSymbols) EXPECT_NO_THROW(parser.Parse(srcOneNS)); // All symbols - std::string ssAllNS = R"code(NS_ : NS_DESC_ CM_ BA_DEF_ BA_ VAL_ CAT_DEF_ CAT_ FILTER BA_DEF_DEF_ EV_DATA_ + std::string ssAllNS = R"dbc(NS_ : NS_DESC_ CM_ BA_DEF_ BA_ VAL_ CAT_DEF_ CAT_ FILTER BA_DEF_DEF_ EV_DATA_ ENVVAR_DATA_ SGTYPE_ SGTYPE_VAL_ BA_DEF_SGTYPE_ BA_SGTYPE_ SIG_TYPE_REF_ VAL_TABLE_ SIG_GROUP_ SIG_VALTYPE_ SIGTYPE_VALTYPE_ BO_TX_BU_ BA_DEF_REL_ BA_REL_ BA_DEF_DEF_REL_ BU_SG_REL_ - BU_EV_REL_ BU_BO_REL_ SG_MUL_VAL_)code"; + BU_EV_REL_ BU_BO_REL_ SG_MUL_VAL_)dbc"; dbc::CDbcSource srcAllNS(ssAllNS); EXPECT_NO_THROW(parser.Parse(srcAllNS)); @@ -628,7 +628,7 @@ TEST_F(CDbcParserTest, SignalDef) EXPECT_TRUE(parser.HasNodeDef("nodeRx2")); // Message definition - std::string ssValidMsgDef1 = R"code( + std::string ssValidMsgDef1 = R"dbc( BO_ 1 msg_big_endian: 8 nodeTx SG_ sig1 : 7|4@0+ (1,0) [0|8191] "Nm" nodeRx1 SG_ sig2 : 3|8@0+ (1,0) [0|8191] "Nm" nodeRx1, nodeRx2 @@ -651,7 +651,7 @@ TEST_F(CDbcParserTest, SignalDef) SG_ sig5a m 1: 24|8@1+ (1,0) [0|8191] "Nm" Vector__XXX SG_ sig2b m 0: 4|32@1+ (1,0) [0|8191] "Nm" nodeRx1, nodeRx2 SG_ sig6 : 32|32@1+ (1,0) [0|8191] "Nm" Vector__XXX -)code"; +)dbc"; dbc::CDbcSource srcValidMsgDef1(ssValidMsgDef1); EXPECT_NO_THROW(parser.Parse(srcValidMsgDef1)); EXPECT_TRUE(parser.HasSignalDef("msg_big_endian", "sig1")); @@ -725,32 +725,32 @@ TEST_F(CDbcParserTest, SignalDef) EXPECT_TRUE(parser.GetSignalDefExtId(3, "sig6").second); // Duplicate signals - std::string ssDuplicateSigDef1 = R"code( + std::string ssDuplicateSigDef1 = R"dbc( BO_ 4 msg4: 8 nodeTx SG_ sig1 : 52|13@0+ (1,0) [0|8191] "Nm" nodeRx1 SG_ sig2 : 36|13@0+ (1,0) [0|8191] "Nm" nodeRx1, nodeRx2 SG_ sig1 : 18|13@0+ (1,0) [0|8191] "Nm" Vector__XXX - )code"; + )dbc"; dbc::CDbcSource srcDuplicateSigDef1(ssDuplicateSigDef1); EXPECT_THROW(parser.Parse(srcDuplicateSigDef1), dbc::SDbcParserException); // Invalid signals - std::string ssInvalidStartBit = R"code( + std::string ssInvalidStartBit = R"dbc( BO_ 5 msg5: 8 nodeTx SG_ sig1 : 64|13@0+ (1,0) [0|8191] "Nm" nodeRx1 - )code"; + )dbc"; dbc::CDbcSource srcInvalidStartBit(ssInvalidStartBit); EXPECT_THROW(parser.Parse(srcInvalidStartBit), dbc::SDbcParserException); - std::string ssInvalidLengthLittleEndian = R"code( + std::string ssInvalidLengthLittleEndian = R"dbc( BO_ 6 msg6: 8 nodeTx SG_ sig1 : 33|32@1+ (1,0) [0|8191] "Nm" nodeRx1 - )code"; + )dbc"; dbc::CDbcSource srcInvalidLengthLittleEndian(ssInvalidLengthLittleEndian); EXPECT_THROW(parser.Parse(srcInvalidLengthLittleEndian), dbc::SDbcParserException); - std::string ssInvalidLengthBigEndian = R"code( + std::string ssInvalidLengthBigEndian = R"dbc( BO_ 7 msg7: 8 nodeTx SG_ sig1 : 38|32@0+ (1,0) [0|8191] "Nm" nodeRx1 - )code"; + )dbc"; dbc::CDbcSource srcInvalidLengthBigEndian(ssInvalidLengthBigEndian); EXPECT_THROW(parser.Parse(srcInvalidLengthBigEndian), dbc::SDbcParserException); } @@ -767,13 +767,13 @@ TEST_F(CDbcParserTest, SignalTypeDef) EXPECT_TRUE(parser.HasNodeDef("nodeRx")); // Message definition - std::string ssMsgDef = R"code( + std::string ssMsgDef = R"dbc( BO_ 1 msg_float: 8 nodeTx SG_ sig_float : 0|32@1- (1,0) [0|8191] "Nm" nodeRx SG_ sig_int : 32|32@1+ (1,0) [0|8191] "Nm" nodeRx BO_ 2 msg_double: 8 nodeTx SG_ sig_double : 0|64@1- (1,0) [0|8191] "Nm" nodeRx - )code"; + )dbc"; dbc::CDbcSource srcMsgDef(ssMsgDef); EXPECT_NO_THROW(parser.Parse(srcMsgDef)); EXPECT_TRUE(parser.HasMsgDef("msg_float")); @@ -789,11 +789,11 @@ TEST_F(CDbcParserTest, SignalTypeDef) EXPECT_EQ(prSigDouble.first.eValType, dbc::SSignalDef::EValueType::signed_integer); // Set the signal value types. - std::string ssSigValType = R"code( + std::string ssSigValType = R"dbc( SIG_VALTYPE_ 1 sig_float 1; SIG_VALTYPE_ 1 sig_int 0; SIG_VALTYPE_ 2 sig_double 2; - )code"; + )dbc"; dbc::CDbcSource srcSigValType(ssSigValType); EXPECT_NO_THROW(parser.Parse(srcSigValType)); @@ -877,11 +877,11 @@ TEST_F(CDbcParserTest, SignalValueDescriptions) EXPECT_TRUE(parser.HasNodeDef("nodeRx")); // Message definition - std::string ssMsgDef = R"code( + std::string ssMsgDef = R"dbc( BO_ 1 msg_enum: 8 nodeTx SG_ sig_enum : 0|32@1+ (1,0) [0|0] "" nodeRx SG_ sig_enum_empty : 0|32@1+ (1,0) [0|0] "" nodeRx - )code"; + )dbc"; dbc::CDbcSource srcMsgDef(ssMsgDef); EXPECT_NO_THROW(parser.Parse(srcMsgDef)); EXPECT_TRUE(parser.HasMsgDef("msg_enum")); @@ -933,11 +933,11 @@ TEST_F(CDbcParserTest, EnvironmentVariableDef) EXPECT_TRUE(parser.HasNodeDef("nodeRx")); // Environment variable definition - std::string ssEnvVarDef = R"code( + std::string ssEnvVarDef = R"dbc( EV_ var1 : 0 [10|20] "steps" 15 100 DUMMY_NODE_VECTOR3 nodeTx, nodeRx; EV_ var2 : 1 [0.1|0.2] "tenth steps" 0.5 101 DUMMY_NODE_VECTOR2 nodeTx; EV_ var3 : 2 [0.0|0.0] "string val" 0.0 102 DUMMY_NODE_VECTOR8001 nodeRx; -)code"; +)dbc"; dbc::CDbcSource srcEnvVarDef(ssEnvVarDef); EXPECT_NO_THROW(parser.Parse(srcEnvVarDef)); auto vecEnvVars = parser.GetEnvVarNames(); @@ -996,18 +996,18 @@ TEST_F(CDbcParserTest, EnvVarData) EXPECT_TRUE(parser.HasNodeDef("nodeRx")); // Environment variable definition - std::string ssEnvVarDef = R"code( + std::string ssEnvVarDef = R"dbc( EV_ var1 : 0 [10|20] "steps" 15 100 DUMMY_NODE_VECTOR3 nodeTx, nodeRx; EV_ var2 : 1 [0.1|0.2] "tenth steps" 0.5 101 DUMMY_NODE_VECTOR2 nodeTx; EV_ var3 : 2 [0.0|0.0] "string val" 0.0 102 DUMMY_NODE_VECTOR8001 nodeRx; -)code"; +)dbc"; dbc::CDbcSource srcEnvVarDef(ssEnvVarDef); EXPECT_NO_THROW(parser.Parse(srcEnvVarDef)); // Environment variable data definition - std::string ssEnvVarDataDef = R"code( + std::string ssEnvVarDataDef = R"dbc( ENVVAR_DATA_ var1 : 10; -)code"; +)dbc"; dbc::CDbcSource srcEnvVarDataDef(ssEnvVarDataDef); EXPECT_NO_THROW(parser.Parse(srcEnvVarDataDef)); auto prEnvVar = parser.GetEnvVarDef("var1"); @@ -1038,11 +1038,11 @@ TEST_F(CDbcParserTest, EnvVarValueDescriptions) EXPECT_TRUE(parser.HasNodeDef("nodeRx")); // Environment variable definition - std::string ssEnvVarDef = R"code( + std::string ssEnvVarDef = R"dbc( EV_ var1 : 0 [10|20] "steps" 15 100 DUMMY_NODE_VECTOR3 nodeTx, nodeRx; EV_ var2 : 1 [0.1|0.2] "tenth steps" 0.5 101 DUMMY_NODE_VECTOR2 nodeTx; EV_ var3 : 2 [0.0|0.0] "string val" 0.0 102 DUMMY_NODE_VECTOR8001 nodeRx; -)code"; +)dbc"; dbc::CDbcSource srcEnvVarDef(ssEnvVarDef); EXPECT_NO_THROW(parser.Parse(srcEnvVarDef)); @@ -1079,17 +1079,17 @@ TEST_F(CDbcParserTest, ExtSignalTypeDef) dbc::CDbcParser parser; // Value table - std::string ssValueTable = R"code( + std::string ssValueTable = R"dbc( VAL_TABLE_ table1 10 "ten" 20 "twenty"; -)code"; +)dbc"; dbc::CDbcSource srcValueTable(ssValueTable); EXPECT_NO_THROW(parser.Parse(srcValueTable)); // Value table type definition - std::string ssValueTableType = R"code( + std::string ssValueTableType = R"dbc( SGTYPE_ sgtype1 : 32 @ 1+ (1.0, 0) [0|100] "signal unit" 50, table1; SGTYPE_ sgtype2 : 16 @ 0- (-10.5, 10) [-100|200] "signal unit2" 13, Vector__XXX; -)code"; +)dbc"; dbc::CDbcSource srcValueTableType(ssValueTableType); EXPECT_NO_THROW(parser.Parse(srcValueTableType)); auto vecSignalTypes = parser.GetSignalTypeDefNames(); @@ -1129,17 +1129,17 @@ TEST_F(CDbcParserTest, SignalTypeRef) dbc::CDbcParser parser; // Value table - std::string ssValueTable = R"code( + std::string ssValueTable = R"dbc( VAL_TABLE_ table1 10 "ten" 20 "twenty"; VAL_TABLE_ table2 20 "twenty" 30 "thirty"; -)code"; +)dbc"; dbc::CDbcSource srcValueTable(ssValueTable); EXPECT_NO_THROW(parser.Parse(srcValueTable)); // Signal type definition - std::string ssSignalType = R"code( + std::string ssSignalType = R"dbc( SGTYPE_ sgtype1 : 32 @ 1+ (1.0, 0) [0|100] "signal unit" 50, table1; -)code"; +)dbc"; dbc::CDbcSource srcSignalType(ssSignalType); EXPECT_NO_THROW(parser.Parse(srcSignalType)); @@ -1151,11 +1151,11 @@ TEST_F(CDbcParserTest, SignalTypeRef) EXPECT_TRUE(parser.HasNodeDef("nodeRx")); // Message definition - std::string ssMsgDef = R"code( + std::string ssMsgDef = R"dbc( BO_ 1 msg: 8 nodeTx SG_ sig1 : 0|32@1+ (1,0) [0|0] "" nodeRx SG_ sig2 : 0|32@1+ (1,0) [0|0] "" nodeRx - )code"; + )dbc"; dbc::CDbcSource srcMsgDef(ssMsgDef); EXPECT_NO_THROW(parser.Parse(srcMsgDef)); @@ -1180,20 +1180,20 @@ TEST_F(CDbcParserTest, SignalGroupDef) EXPECT_TRUE(parser.HasNodeDef("nodeRx")); // Message definition - std::string ssMsgDef = R"code( + std::string ssMsgDef = R"dbc( BO_ 1 msg: 8 nodeTx SG_ sig1 : 0|32@1+ (1,0) [0|0] "" nodeRx SG_ sig2 : 0|32@1+ (1,0) [0|0] "" nodeRx SG_ sig3 : 0|32@1+ (1,0) [0|0] "" nodeRx - )code"; + )dbc"; dbc::CDbcSource srcMsgDef(ssMsgDef); EXPECT_NO_THROW(parser.Parse(srcMsgDef)); // Signal group definition - std::string ssSignalGroup = R"code( + std::string ssSignalGroup = R"dbc( SIG_GROUP_ 1 group1 10 : sig1 sig2; SIG_GROUP_ 1 group2 20 : sig3; - )code"; + )dbc"; dbc::CDbcSource srcSignalGroup(ssSignalGroup); EXPECT_NO_THROW(parser.Parse(srcSignalGroup)); auto vecGroupNames = parser.GetSignalGroupDefNames(1); @@ -1220,12 +1220,12 @@ TEST_F(CDbcParserTest, GlobalComments) dbc::CDbcParser parser; // Global comments - std::string ssComments = R"code( + std::string ssComments = R"dbc( CM_ "first comment"; CM_ "second comment"; CM_ "third comment"; CM_ "fourth comment"; - )code"; + )dbc"; dbc::CDbcSource srcComments(ssComments); EXPECT_NO_THROW(parser.Parse(srcComments)); auto vecComments = parser.GetComments(); @@ -1248,12 +1248,12 @@ TEST_F(CDbcParserTest, NodeComments) EXPECT_TRUE(parser.HasNodeDef("nodeRx")); // Node comments - std::string ssComments = R"code( + std::string ssComments = R"dbc( CM_ BU_ nodeTx "first comment"; CM_ BU_ nodeTx "second comment \"with quotes\""; CM_ BU_ nodeRx "third comment 'with single quotes'"; CM_ BU_ nodeRx "fourth comment"; - )code"; + )dbc"; dbc::CDbcSource srcComments(ssComments); EXPECT_NO_THROW(parser.Parse(srcComments)); auto prNode = parser.GetNodeDef("nodeTx"); @@ -1280,22 +1280,22 @@ TEST_F(CDbcParserTest, MessageComments) EXPECT_TRUE(parser.HasNodeDef("nodeRx")); // Message definition - std::string ssMsgDef = R"code( + std::string ssMsgDef = R"dbc( BO_ 1 msg: 8 nodeTx SG_ sig1 : 0|32@1+ (1,0) [0|0] "" nodeRx SG_ sig2 : 0|32@1+ (1,0) [0|0] "" nodeRx SG_ sig3 : 0|32@1+ (1,0) [0|0] "" nodeRx - )code"; + )dbc"; dbc::CDbcSource srcMsgDef(ssMsgDef); EXPECT_NO_THROW(parser.Parse(srcMsgDef)); // Message comments - std::string ssComments = R"code( + std::string ssComments = R"dbc( CM_ BO_ 1 "first comment"; CM_ BO_ 1 "second comment"; CM_ BO_ 1 "third comment"; CM_ BO_ 1 "fourth comment"; - )code"; + )dbc"; dbc::CDbcSource srcComments(ssComments); EXPECT_NO_THROW(parser.Parse(srcComments)); auto prMsgDef = parser.GetMsgDef(1); @@ -1319,22 +1319,22 @@ TEST_F(CDbcParserTest, SignalComments) EXPECT_TRUE(parser.HasNodeDef("nodeRx")); // Signal definition - std::string ssMsgDef = R"code( + std::string ssMsgDef = R"dbc( BO_ 1 msg: 8 nodeTx SG_ sig1 : 0|32@1+ (1,0) [0|0] "" nodeRx SG_ sig2 : 0|32@1+ (1,0) [0|0] "" nodeRx SG_ sig3 : 0|32@1+ (1,0) [0|0] "" nodeRx - )code"; + )dbc"; dbc::CDbcSource srcMsgDef(ssMsgDef); EXPECT_NO_THROW(parser.Parse(srcMsgDef)); // Node comments - std::string ssComments = R"code( + std::string ssComments = R"dbc( CM_ SG_ 1 sig1 "first comment"; CM_ SG_ 1 sig1 "second comment"; CM_ SG_ 1 sig2 "third comment"; CM_ SG_ 1 sig3 "fourth comment"; - )code"; + )dbc"; dbc::CDbcSource srcComments(ssComments); EXPECT_NO_THROW(parser.Parse(srcComments)); auto prSignal = parser.GetSignalDef(1, "sig1"); @@ -1364,21 +1364,21 @@ TEST_F(CDbcParserTest, EnvVarComments) EXPECT_TRUE(parser.HasNodeDef("nodeRx")); // Environment variable definition - std::string ssEnvVarDef = R"code( + std::string ssEnvVarDef = R"dbc( EV_ var1 : 0 [10|20] "steps" 15 100 DUMMY_NODE_VECTOR3 nodeTx, nodeRx; EV_ var2 : 1 [0.1|0.2] "tenth steps" 0.5 101 DUMMY_NODE_VECTOR2 nodeTx; EV_ var3 : 2 [0.0|0.0] "string val" 0.0 102 DUMMY_NODE_VECTOR8001 nodeRx; -)code"; +)dbc"; dbc::CDbcSource srcEnvVarDef(ssEnvVarDef); EXPECT_NO_THROW(parser.Parse(srcEnvVarDef)); // Env var comments - std::string ssComments = R"code( + std::string ssComments = R"dbc( CM_ EV_ var1 "first comment"; CM_ EV_ var1 "second comment"; CM_ EV_ var2 "third comment"; CM_ EV_ var3 "fourth comment"; - )code"; + )dbc"; dbc::CDbcSource srcComments(ssComments); EXPECT_NO_THROW(parser.Parse(srcComments)); auto prEnvVar = parser.GetEnvVarDef("var1"); @@ -1402,14 +1402,14 @@ TEST_F(CDbcParserTest, AttributeDef) // Global attribute definition // The use of "n/a" and "not-used" is not part of the official standard. - std::string ssAttribute = R"code( + std::string ssAttribute = R"dbc( BA_DEF_ "attr1" INT 10 20; BA_DEF_ "attr2" HEX 10 20; BA_DEF_ "attr3" FLOAT 1.0 10.9; BA_DEF_ "attr4" STRING; BA_DEF_ "attr5" ENUM "abc", "def", "ghi"; BA_DEF_ "attr6" ENUM "abc", "n/a", "not-used", "jkl", "n/a", "not-used", "stu"; - )code"; + )dbc"; dbc::CDbcSource srcAttribute(ssAttribute); EXPECT_NO_THROW(parser.Parse(srcAttribute)); auto vecAttr = parser.GetAttributeDefNames(); @@ -1470,27 +1470,27 @@ TEST_F(CDbcParserTest, AttributeDefaultValDef) // Global attribute definition // The use of "n/a" and "not-used" is not part of the official standard. - std::string ssAttribute = R"code( + std::string ssAttribute = R"dbc( BA_DEF_ "attr1" INT 10 20; BA_DEF_ "attr2" HEX 10 20; BA_DEF_ "attr3" FLOAT 1.0 10.9; BA_DEF_ "attr4" STRING; BA_DEF_ "attr5" ENUM "abc", "def", "ghi"; BA_DEF_ "attr6" ENUM "abc", "n/a", "not-used", "jkl", "n/a", "not-used", "stu"; - )code"; + )dbc"; dbc::CDbcSource srcAttribute(ssAttribute); EXPECT_NO_THROW(parser.Parse(srcAttribute)); // Attribute def default value // The use of an index instead of a value for the enum value is not part of the official standard. - std::string ssAttrDefDefaultVal = R"code( + std::string ssAttrDefDefaultVal = R"dbc( BA_DEF_DEF_ "attr1" 15; BA_DEF_DEF_ "attr2" 15; BA_DEF_DEF_ "attr3" 5.5; BA_DEF_DEF_ "attr4" "hello"; BA_DEF_DEF_ "attr5" "def"; BA_DEF_DEF_ "attr6" 6; - )code"; + )dbc"; dbc::CDbcSource srcAttrDefDefaultVal(ssAttrDefDefaultVal); EXPECT_NO_THROW(parser.Parse(srcAttrDefDefaultVal)); @@ -1514,20 +1514,20 @@ TEST_F(CDbcParserTest, GlobalAttributeVal) // Global attribute definition // The use of "n/a" and "not-used" is not part of the official standard. - std::string ssAttribute = R"code( + std::string ssAttribute = R"dbc( BA_DEF_ "attr1" INT 10 20; BA_DEF_ "attr2" HEX 10 20; BA_DEF_ "attr3" FLOAT 1.0 10.9; BA_DEF_ "attr4" STRING; BA_DEF_ "attr5" ENUM "abc", "def", "ghi"; BA_DEF_ "attr6" ENUM "abc", "n/a", "not-used", "jkl", "n/a", "not-used", "stu"; - )code"; + )dbc"; dbc::CDbcSource srcAttribute(ssAttribute); EXPECT_NO_THROW(parser.Parse(srcAttribute)); // Attribute value // The use of an index instead of a value for the enum value is not part of the official standard. - std::string ssAttrVal = R"code( + std::string ssAttrVal = R"dbc( BA_ "attr1" 11; BA_ "attr2" 12; BA_ "attr2" 13; @@ -1535,7 +1535,7 @@ TEST_F(CDbcParserTest, GlobalAttributeVal) BA_ "attr4" "hi"; BA_ "attr5" "ghi"; BA_ "attr6" 6; - )code"; + )dbc"; dbc::CDbcSource srcAttrVal(ssAttrVal); EXPECT_NO_THROW(parser.Parse(srcAttrVal)); auto vecAttr = parser.GetAttributes(); @@ -1562,20 +1562,20 @@ TEST_F(CDbcParserTest, NodeAttributeVal) // Node attribute definition // The use of "n/a" and "not-used" is not part of the official standard. - std::string ssAttribute = R"code( + std::string ssAttribute = R"dbc( BA_DEF_ BU_ "attr1" INT 10 20; BA_DEF_ BU_ "attr2" HEX 10 20; BA_DEF_ BU_ "attr3" FLOAT 1.0 10.9; BA_DEF_ BU_ "attr4" STRING; BA_DEF_ BU_ "attr5" ENUM "abc", "def", "ghi"; BA_DEF_ BU_ "attr6" ENUM "abc", "n/a", "not-used", "jkl", "n/a", "not-used", "stu"; - )code"; + )dbc"; dbc::CDbcSource srcAttribute(ssAttribute); EXPECT_NO_THROW(parser.Parse(srcAttribute)); // Attribute value // The use of an index instead of a value for the enum value is not part of the official standard. - std::string ssAttrVal = R"code( + std::string ssAttrVal = R"dbc( BA_ "attr1" BU_ nodeTx 11; BA_ "attr2" BU_ nodeTx 12; BA_ "attr2" BU_ nodeTx 13; @@ -1583,7 +1583,7 @@ TEST_F(CDbcParserTest, NodeAttributeVal) BA_ "attr4" BU_ nodeRx "hi"; BA_ "attr5" BU_ nodeRx "ghi"; BA_ "attr6" BU_ nodeRx 6; - )code"; + )dbc"; dbc::CDbcSource srcAttrVal(ssAttrVal); EXPECT_NO_THROW(parser.Parse(srcAttrVal)); auto prNodeDef = parser.GetNodeDef("nodeTx"); @@ -1612,31 +1612,31 @@ TEST_F(CDbcParserTest, MessageAttributeVal) // Message attribute definition // The use of "n/a" and "not-used" is not part of the official standard. - std::string ssAttribute = R"code( + std::string ssAttribute = R"dbc( BA_DEF_ BO_ "attr1" INT 10 20; BA_DEF_ BO_ "attr2" HEX 10 20; BA_DEF_ BO_ "attr3" FLOAT 1.0 10.9; BA_DEF_ BO_ "attr4" STRING; BA_DEF_ BO_ "attr5" ENUM "abc", "def", "ghi"; BA_DEF_ BO_ "attr6" ENUM "abc", "n/a", "not-used", "jkl", "n/a", "not-used", "stu"; - )code"; + )dbc"; dbc::CDbcSource srcAttribute(ssAttribute); EXPECT_NO_THROW(parser.Parse(srcAttribute)); // Message definition - std::string ssMsgDef = R"code( + std::string ssMsgDef = R"dbc( BO_ 1 msg: 8 nodeTx SG_ sig1 : 0|32@1+ (1,0) [0|0] "" nodeRx SG_ sig2 : 0|32@1+ (1,0) [0|0] "" nodeRx SG_ sig3 : 0|32@1+ (1,0) [0|0] "" nodeRx BO_ 2 msg2: 8 nodeRx - )code"; + )dbc"; dbc::CDbcSource srcMsgDef(ssMsgDef); EXPECT_NO_THROW(parser.Parse(srcMsgDef)); // Attribute value // The use of an index instead of a value for the enum value is not part of the official standard. - std::string ssAttrVal = R"code( + std::string ssAttrVal = R"dbc( BA_ "attr1" BO_ 1 11; BA_ "attr2" BO_ 1 12; BA_ "attr2" BO_ 1 13; @@ -1644,7 +1644,7 @@ TEST_F(CDbcParserTest, MessageAttributeVal) BA_ "attr4" BO_ 2 "hi"; BA_ "attr5" BO_ 2 "ghi"; BA_ "attr6" BO_ 2 6; - )code"; + )dbc"; dbc::CDbcSource srcAttrVal(ssAttrVal); EXPECT_NO_THROW(parser.Parse(srcAttrVal)); auto prMsgDef = parser.GetMsgDef(1); @@ -1673,31 +1673,31 @@ TEST_F(CDbcParserTest, SignalAttributeVal) // Signal attribute definition // The use of "n/a" and "not-used" is not part of the official standard. - std::string ssAttribute = R"code( + std::string ssAttribute = R"dbc( BA_DEF_ SG_ "attr1" INT 10 20; BA_DEF_ SG_ "attr2" HEX 10 20; BA_DEF_ SG_ "attr3" FLOAT 1.0 10.9; BA_DEF_ SG_ "attr4" STRING; BA_DEF_ SG_ "attr5" ENUM "abc", "def", "ghi"; BA_DEF_ SG_ "attr6" ENUM "abc", "n/a", "not-used", "jkl", "n/a", "not-used", "stu"; - )code"; + )dbc"; dbc::CDbcSource srcAttribute(ssAttribute); EXPECT_NO_THROW(parser.Parse(srcAttribute)); // Message definition - std::string ssMsgDef = R"code( + std::string ssMsgDef = R"dbc( BO_ 1 msg: 8 nodeTx SG_ sig1 : 0|32@1+ (1,0) [0|0] "" nodeRx SG_ sig2 : 0|32@1+ (1,0) [0|0] "" nodeRx SG_ sig3 : 0|32@1+ (1,0) [0|0] "" nodeRx BO_ 2 msg2: 8 nodeRx - )code"; + )dbc"; dbc::CDbcSource srcMsgDef(ssMsgDef); EXPECT_NO_THROW(parser.Parse(srcMsgDef)); // Attribute value // The use of an index instead of a value for the enum value is not part of the official standard. - std::string ssAttrVal = R"code( + std::string ssAttrVal = R"dbc( BA_ "attr1" SG_ 1 sig1 11; BA_ "attr2" SG_ 1 sig1 12; BA_ "attr2" SG_ 1 sig2 13; @@ -1705,7 +1705,7 @@ TEST_F(CDbcParserTest, SignalAttributeVal) BA_ "attr4" SG_ 1 sig3 "hi"; BA_ "attr5" SG_ 1 sig3 "ghi"; BA_ "attr6" SG_ 1 sig3 6; - )code"; + )dbc"; dbc::CDbcSource srcAttrVal(ssAttrVal); EXPECT_NO_THROW(parser.Parse(srcAttrVal)); auto prSigDef = parser.GetSignalDef(1, "sig1"); @@ -1736,29 +1736,29 @@ TEST_F(CDbcParserTest, EnvVarAttributeVal) // Environment variable attribute definition // The use of "n/a" and "not-used" is not part of the official standard. - std::string ssAttribute = R"code( + std::string ssAttribute = R"dbc( BA_DEF_ EV_ "attr1" INT 10 20; BA_DEF_ EV_ "attr2" HEX 10 20; BA_DEF_ EV_ "attr3" FLOAT 1.0 10.9; BA_DEF_ EV_ "attr4" STRING; BA_DEF_ EV_ "attr5" ENUM "abc", "def", "ghi"; BA_DEF_ EV_ "attr6" ENUM "abc", "n/a", "not-used", "jkl", "n/a", "not-used", "stu"; - )code"; + )dbc"; dbc::CDbcSource srcAttribute(ssAttribute); EXPECT_NO_THROW(parser.Parse(srcAttribute)); // Environment variable definition - std::string ssEnvVarDef = R"code( + std::string ssEnvVarDef = R"dbc( EV_ var1 : 0 [10|20] "steps" 15 100 DUMMY_NODE_VECTOR3 nodeTx, nodeRx; EV_ var2 : 1 [0.1|0.2] "tenth steps" 0.5 101 DUMMY_NODE_VECTOR2 nodeTx; EV_ var3 : 2 [0.0|0.0] "string val" 0.0 102 DUMMY_NODE_VECTOR8001 nodeRx; -)code"; +)dbc"; dbc::CDbcSource srcEnvVarDef(ssEnvVarDef); EXPECT_NO_THROW(parser.Parse(srcEnvVarDef)); // Attribute value // The use of an index instead of a value for the enum value is not part of the official standard. - std::string ssAttrVal = R"code( + std::string ssAttrVal = R"dbc( BA_ "attr1" EV_ var1 11; BA_ "attr2" EV_ var1 12; BA_ "attr2" EV_ var2 13; @@ -1766,7 +1766,7 @@ TEST_F(CDbcParserTest, EnvVarAttributeVal) BA_ "attr4" EV_ var3 "hi"; BA_ "attr5" EV_ var3 "ghi"; BA_ "attr6" EV_ var3 6; - )code"; + )dbc"; dbc::CDbcSource srcAttrVal(ssAttrVal); EXPECT_NO_THROW(parser.Parse(srcAttrVal)); auto prEnvVarDef = parser.GetEnvVarDef("var1"); @@ -1789,7 +1789,7 @@ TEST_F(CDbcParserTest, SignalExtendedMultiplexing) dbc::CDbcParser parser; // Message definition - std::string ssMsgDef = R"code( + std::string ssMsgDef = R"dbc( BO_ 100 MuxMsg: 1 Vector__XXX SG_ Mux_4 m2 : 6|2@1+ (1,0) [0|0] "" Vector__XXX SG_ Mux_3 m3M : 4|2@1+ (1,0) [0|0] "" Vector__XXX @@ -1799,7 +1799,7 @@ TEST_F(CDbcParserTest, SignalExtendedMultiplexing) SG_MUL_VAL_ 100 Mux_2 Mux_1 3-3, 5-10; SG_MUL_VAL_ 100 Mux_3 Mux_2 3-3; SG_MUL_VAL_ 100 Mux_4 Mux_3 2-2; - )code"; + )dbc"; dbc::CDbcSource srcMsgDef(ssMsgDef); EXPECT_NO_THROW(parser.Parse(srcMsgDef)); @@ -1842,7 +1842,7 @@ TEST_F(CDbcParserTest, ExampleDBC1) dbc::CDbcParser parser; // Message definition - std::string ssMsgDef = R"code( + std::string ssMsgDef = R"dbc( VERSION "" NS_ : @@ -1891,7 +1891,7 @@ CM_ "CAN communication matrix for power train electronics implemented: turn lights, warning lights, windows"; VAL_ 100 IdleRunning 0 "Running" 1 "Idle" ; - )code"; + )dbc"; dbc::CDbcSource srcMsgDef(ssMsgDef); EXPECT_NO_THROW(parser.Parse(srcMsgDef)); } @@ -1900,7 +1900,7 @@ TEST_F(CDbcParserTest, ExampleDBC2) dbc::CDbcParser parser; // Message definition - std::string ssMsgDef = R"code( + std::string ssMsgDef = R"dbc( VERSION "" @@ -2023,7 +2023,7 @@ VAL_ 1 HMI_Lateral_Long_Ctrl_Req 3 "LONGITUDINAL_CTRL_REQ" 2 "LONGITUDINAL_AND_L VAL_ 1 HMI_Algo_Variant_Request 4 "NONE" 3 "VARIANT_4" 2 "VARIANT_3" 1 "VARIANT_2" 0 "VARIANT_1" ; VAL_ 0 MAB_AI4Motion_Algo_Var_Selected 4 "NONE" 3 "VARIANT_4" 2 "VARIANT_3" 1 "VARIANT_2" 0 "VARIANT_1" ; VAL_ 0 MAB_Activation_AI4Motion 3 "LONGITUDINAL_CTRL_REQ" 2 "LONGITUDINAL_AND_LATERAL" 1 "LATERAL_CTRL_REQ" 0 "NONE" ; -SIG_VALTYPE_ 3221225472 New_Signal_25 : 2; )code"; +SIG_VALTYPE_ 3221225472 New_Signal_25 : 2; )dbc"; dbc::CDbcSource srcMsgDef(ssMsgDef); EXPECT_NO_THROW(parser.Parse(srcMsgDef)); } @@ -2032,7 +2032,7 @@ TEST_F(CDbcParserTest, ExampleDBC3) dbc::CDbcParser parser; // Message definition - std::string ssMsgDef = R"code( + std::string ssMsgDef = R"dbc( VERSION "" @@ -2135,7 +2135,7 @@ VERSION "" VAL_ 100 DRIVER_HEARTBEAT_cmd 2 "DRIVER_HEARTBEAT_cmd_REBOOT" 1 "DRIVER_HEARTBEAT_cmd_SYNC" 0 "DRIVER_HEARTBEAT_cmd_NOOP" ; VAL_ 500 IO_DEBUG_test_enum 2 "IO_DEBUG_test2_enum_two" 1 "IO_DEBUG_test2_enum_one" ; - )code"; + )dbc"; dbc::CDbcSource srcMsgDef(ssMsgDef); EXPECT_NO_THROW(parser.Parse(srcMsgDef)); } diff --git a/tests/unit_tests/install_package_composer/composer_test_suite.cpp b/tests/unit_tests/install_package_composer/composer_test_suite.cpp index 29bc8ad..462e83c 100644 --- a/tests/unit_tests/install_package_composer/composer_test_suite.cpp +++ b/tests/unit_tests/install_package_composer/composer_test_suite.cpp @@ -27,6 +27,7 @@ #include "../../../sdv_services/core/toml_parser/character_reader_utf_8.cpp" #include "../../../sdv_services/core/toml_parser/miscellaneous.cpp" #include "../../../sdv_services/core/toml_parser/code_snippet.cpp" +#include "../../../sdv_services/core/toml_parser/parser_node_indexer.cpp" #include #if defined(_WIN32) && defined(_UNICODE) diff --git a/tests/unit_tests/ipc_com/CMakeLists.txt b/tests/unit_tests/ipc_com/CMakeLists.txt index 804d20c..d3f5d6a 100644 --- a/tests/unit_tests/ipc_com/CMakeLists.txt +++ b/tests/unit_tests/ipc_com/CMakeLists.txt @@ -45,8 +45,10 @@ set_target_properties(UnitTest_IPC_Communication_ps PROPERTIES SUFFIX ".sdv") # Compile the source code add_executable(UnitTest_IPC_Communication "main.cpp" - - "ipc_com.cpp" + "ipc_com_sharedmem.cpp" + "ipc_com_uds.cpp" + "include.h" + "ipc_com_uds_tunnel.cpp" ) target_link_libraries(UnitTest_IPC_Communication ${CMAKE_DL_LIBS} GTest::GTest) diff --git a/tests/unit_tests/ipc_com/include.h b/tests/unit_tests/ipc_com/include.h new file mode 100644 index 0000000..feb182d --- /dev/null +++ b/tests/unit_tests/ipc_com/include.h @@ -0,0 +1,1300 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#include +#include +#include + +#include "../../../sdv_services/ipc_com/com_channel.h" +#include "../../../sdv_services/ipc_com/com_ctrl.h" +#include "../../../sdv_services/ipc_com/marshall_object.h" +#include "../../include/gtest_custom.h" +#include "generated/test_ifc.h" + +inline EHello& operator++(EHello& reHello, int) +{ + switch (reHello) + { + case EHello::hi: + reHello = EHello::huhu; + break; + case EHello::huhu: + reHello = EHello::hello; + break; + case EHello::hello: + reHello = EHello::hallo; + break; + case EHello::hallo: + reHello = EHello::servus; + break; + case EHello::servus: + reHello = EHello::gruessgott; + break; + case EHello::gruessgott: + reHello = EHello::hi; + break; + default: + break; + } + return reHello; +} + +inline EHello& operator--(EHello& reHello, int) +{ + switch (reHello) + { + case EHello::hi: + reHello = EHello::gruessgott; + break; + case EHello::huhu: + reHello = EHello::hi; + break; + case EHello::hello: + reHello = EHello::huhu; + break; + case EHello::hallo: + reHello = EHello::hello; + break; + case EHello::servus: + reHello = EHello::hallo; + break; + case EHello::gruessgott: + reHello = EHello::servus; + break; + default: + break; + } + return reHello; +} + +/** + * - Instantiate service + * - Assign server endpoint + * - Assign server endpoint + assign client endpoint + * - Create channel endpoint + * - Create channel endpoint + connect channel endpoint + * - Create proxy/stub connection + * - Reconnect + * - Multiple connections + * - Get other interface + * - Multiple access interface + * - Random multiple access interface + * - Random multiple access interface with reconnect + */ + +class CInterfaceTest : + public ISayHello, + public IRequestHello, + public IRegisterHelloCallback, + public IMegaTest, + public sdv::IInterfaceAccess +{ +public: + BEGIN_SDV_INTERFACE_MAP() + SDV_INTERFACE_ENTRY(ISayHello) + SDV_INTERFACE_ENTRY(IRequestHello) + SDV_INTERFACE_ENTRY(IRegisterHelloCallback) + SDV_INTERFACE_ENTRY(IMegaTest) + SDV_INTERFACE_ENTRY(sdv::IInterfaceAccess) + END_SDV_INTERFACE_MAP() + + /** + * @brief Say hello! + * @return The greeting text. + */ + virtual sdv::string Hello() override + { + return "Hello"; + } + + /** + * @brief Request the hello interface. + * @return pHello The hello interface. + */ + virtual ISayHello* Request() + { + return this; + } + + /** + * @brief Register the callback with the hello interface. + * @param[in] pHello The hello interface. + */ + virtual void Register(/*in*/ ISayHello* pHello) + { + m_pHello = pHello; + } + + /** + * @brief Update the mega struct. This will test marshalling and unmarshalling of the mega struct. Overload of + * IMegaTest::ProcessMegaStruct. + * @param[in] rsStruct Reference to the mega struct. + */ + virtual void ProcessMegaStruct(/*inout*/ SMegaStruct& rsStruct) override + { + rsStruct.bVal = !rsStruct.bVal; + rsStruct.sVal--; + rsStruct.usVal++; + rsStruct.lVal--; + rsStruct.ulVal++; + rsStruct.llVal--; + rsStruct.ullVal++; + rsStruct.i8Val--; + rsStruct.ui8Val++; + rsStruct.i16Val--; + rsStruct.ui16Val++; + rsStruct.i32Val--; + rsStruct.ui32Val++; + rsStruct.i64Val--; + rsStruct.ui64Val++; + rsStruct.cVal++; + rsStruct.c16Val++; + rsStruct.c32Val++; + rsStruct.wcVal++; + rsStruct.fVal -= 1000.000f; + rsStruct.dVal += 1000.000; + rsStruct.ldVal -= 1000.000; + rsStruct.nVal++; + rsStruct.eHelloVal = rsStruct.eHelloVal++; + if (rsStruct.idVal == sdv::GetInterfaceId()) + rsStruct.idVal = sdv::GetInterfaceId(); + else + rsStruct.idVal = 0; + rsStruct.ssVal += rsStruct.ssVal.empty() ? 'x' : rsStruct.ssVal.back() + 1; + rsStruct.ss8Val += rsStruct.ss8Val.empty() ? 'x' : rsStruct.ss8Val.back() + 1; + rsStruct.ss16Val += rsStruct.ss16Val.empty() ? u'x' : rsStruct.ss16Val.back() + 1; + rsStruct.ss32Val += rsStruct.ss32Val.empty() ? U'x' : rsStruct.ss32Val.back() + 1; + rsStruct.wssVal += rsStruct.wssVal.empty() ? L'x' : rsStruct.wssVal.back() + 1; + rsStruct.ssFixVal += rsStruct.ssFixVal.empty() ? 'x' : rsStruct.ssFixVal.back() + 1; + rsStruct.ss8FixVal += rsStruct.ss8FixVal.empty() ? 'x' : rsStruct.ss8FixVal.back() + 1; + rsStruct.ss16FixVal += rsStruct.ss16FixVal.empty() ? u'x' : rsStruct.ss16FixVal.back() + 1; + rsStruct.ss32FixVal += rsStruct.ss32FixVal.empty() ? U'x' : rsStruct.ss32FixVal.back() + 1; + rsStruct.wssFixVal += rsStruct.wssFixVal.empty() ? L'x' : rsStruct.wssFixVal.back() + 1; + rsStruct.ptr8Val.resize(rsStruct.ptr8Val.size() * 2); + for (size_t nIndex = 0; nIndex < rsStruct.ptr8Val.size() / 2; nIndex++) + rsStruct.ptr8Val[nIndex + rsStruct.ptr8Val.size() / 2] = rsStruct.ptr8Val[nIndex]; + rsStruct.ptrssVal.resize(rsStruct.ptrssVal.size() * 2); + for (size_t nIndex = 0; nIndex < rsStruct.ptrssVal.size() / 2; nIndex++) + rsStruct.ptrssVal[nIndex + rsStruct.ptrssVal.size() / 2] = rsStruct.ptrssVal[nIndex]; + for (size_t nIndex = 0; nIndex < rsStruct.ptr8FixVal.size(); nIndex++) + rsStruct.ptr8FixVal[nIndex] = static_cast(rsStruct.ptr8FixVal[nIndex] << 1); + for (size_t nIndex = 0; nIndex < rsStruct.ptrssFixVal.size(); nIndex++) + rsStruct.ptrssFixVal[nIndex] = rsStruct.ptrssFixVal[nIndex] + rsStruct.ptrssFixVal[nIndex]; + rsStruct.seq8Val.resize(rsStruct.seq8Val.size() * 2); + for (size_t nIndex = 0; nIndex < rsStruct.seq8Val.size() / 2; nIndex++) + rsStruct.seq8Val[nIndex + rsStruct.seq8Val.size() / 2] = rsStruct.seq8Val[nIndex]; + rsStruct.seqssVal.resize(rsStruct.seqssVal.size() * 2); + for (size_t nIndex = 0; nIndex < rsStruct.seqssVal.size() / 2; nIndex++) + rsStruct.seqssVal[nIndex + rsStruct.seqssVal.size() / 2] = rsStruct.seqssVal[nIndex]; + for (size_t nIndex = 0; nIndex < rsStruct.seq8FixVal.size(); nIndex++) + rsStruct.seq8FixVal[nIndex] = static_cast(rsStruct.seq8FixVal[nIndex] << 1); + for (size_t nIndex = 0; nIndex < rsStruct.seqssFixVal.size(); nIndex++) + rsStruct.seqssFixVal[nIndex] = rsStruct.seqssFixVal[nIndex] + rsStruct.seqssFixVal[nIndex]; + if (rsStruct.ifcVal.get() && rsStruct.ifcVal.id() == sdv::GetInterfaceId()) + { + m_summer.set(rsStruct.ifcVal.get()); + rsStruct.ifcVal = static_cast(&m_summer); + } + else + rsStruct.ifcVal = nullptr; + if (rsStruct.pMultiplyValue) + { + m_multiplier.set(rsStruct.pMultiplyValue); + rsStruct.pMultiplyValue = static_cast(&m_multiplier); + } + else + rsStruct.pMultiplyValue = nullptr; + rsStruct.sSubVal.i += 1; + rsStruct.sIndVal.i += 10; + // rsStruct.sUnnamedVal.i += 20; // 12.04.2024 EVE Unnamed structures are not supported by IDL compiler. + // Unnamed unions (inline definition) are not supported yet: PBI #398209 + // union switch(int32) { case 0: int32 i; default: int32 j; } uUnnamedVal; + // Anonymous structs and unions (unnamed and no declaration) are not supported yet: PBI #397894 + // struct { int32 iAnonymous1; int32 iAnonymous2; }; + // union switch(int32) { case 0: int32 iAnonymous1; default: int32 iAnonymous2; }; + + rsStruct.rgbVal[0] = !rsStruct.rgbVal[0]; + rsStruct.rgbVal[1] = !rsStruct.rgbVal[1]; + rsStruct.rgsVal[0]--; + rsStruct.rgsVal[1]++; + rsStruct.rgusVal[0]++; + rsStruct.rgusVal[1] += 2; + rsStruct.rglVal[0]--; + rsStruct.rglVal[1]++; + rsStruct.rgulVal[0]++; + rsStruct.rgulVal[1] += 2; + rsStruct.rgllVal[0]--; + rsStruct.rgllVal[1]++; + rsStruct.rgullVal[0]++; + rsStruct.rgullVal[1] += 2; + rsStruct.rgi8Val[0]--; + rsStruct.rgi8Val[1]++; + rsStruct.rgui8Val[0]++; + rsStruct.rgui8Val[1] += 2; + rsStruct.rgi16Val[0]--; + rsStruct.rgi16Val[1]++; + rsStruct.rgui16Val[0]++; + rsStruct.rgui16Val[1] += 2; + rsStruct.rgi32Val[0]--; + rsStruct.rgi32Val[1]++; + rsStruct.rgui32Val[0]++; + rsStruct.rgui32Val[1] += 2; + rsStruct.rgi64Val[0]--; + rsStruct.rgi64Val[1]++; + rsStruct.rgui64Val[0]++; + rsStruct.rgui64Val[1] += 2; + rsStruct.rgcVal[0]++; + rsStruct.rgcVal[1] += 2; + rsStruct.rgc16Val[0]++; + rsStruct.rgc16Val[1] += 2; + rsStruct.rgc32Val[0]++; + rsStruct.rgc32Val[1] += 2; + rsStruct.rgwcVal[0]++; + rsStruct.rgwcVal[1] += 2; + rsStruct.rgfVal[0] -= 1000.000f; + rsStruct.rgfVal[1] += 2000.000f; + rsStruct.rgdVal[0] += 1000.000; + rsStruct.rgdVal[1] -= 2000.000; + rsStruct.rgldVal[0] -= 1000.000; + rsStruct.rgldVal[1] += 2000.000; + rsStruct.rgnVal[0]++; + rsStruct.rgnVal[1] += 2; + rsStruct.rgeHelloVal[0] = rsStruct.rgeHelloVal[0]++; + rsStruct.rgeHelloVal[1] = rsStruct.rgeHelloVal[1]--; + if (rsStruct.rgidVal[0] == sdv::GetInterfaceId()) + rsStruct.rgidVal[0] = sdv::GetInterfaceId(); + else + rsStruct.rgidVal[0] = 0; + if (rsStruct.rgidVal[1] == sdv::GetInterfaceId()) + rsStruct.rgidVal[1] = sdv::GetInterfaceId(); + else + rsStruct.rgidVal[1] = 0; + rsStruct.rgssVal[0] += rsStruct.rgssVal[0].empty() ? 'x' : rsStruct.rgssVal[0].back() + 1; + rsStruct.rgssVal[1] += rsStruct.rgssVal[1].empty() ? 'x' : rsStruct.rgssVal[1].front() + 1; + rsStruct.rgss8Val[0] += rsStruct.rgss8Val[0].empty() ? 'x' : rsStruct.rgss8Val[0].back() + 1; + rsStruct.rgss8Val[1] += rsStruct.rgss8Val[1].empty() ? 'x' : rsStruct.rgss8Val[1].front() + 1; + rsStruct.rgss16Val[0] += rsStruct.rgss16Val[0].empty() ? u'x' : rsStruct.rgss16Val[0].back() + 1; + rsStruct.rgss16Val[1] += rsStruct.rgss16Val[1].empty() ? u'x' : rsStruct.rgss16Val[1].front() + 1; + rsStruct.rgss32Val[0] += rsStruct.rgss32Val[0].empty() ? U'x' : rsStruct.rgss32Val[0].back() + 1; + rsStruct.rgss32Val[1] += rsStruct.rgss32Val[1].empty() ? U'x' : rsStruct.rgss32Val[1].front() + 1; + rsStruct.rgwssVal[0] += rsStruct.rgwssVal[0].empty() ? L'x' : rsStruct.rgwssVal[0].back() + 1; + rsStruct.rgwssVal[1] += rsStruct.rgwssVal[1].empty() ? L'x' : rsStruct.rgwssVal[1].front() + 1; + rsStruct.rgssFixVal[0] += rsStruct.rgssFixVal[0].empty() ? 'x' : rsStruct.rgssFixVal[0].back() + 1; + rsStruct.rgssFixVal[1] += rsStruct.rgssFixVal[1].empty() ? 'x' : rsStruct.rgssFixVal[1].front() + 1; + rsStruct.rgss8FixVal[0] += rsStruct.rgss8FixVal[0].empty() ? 'x' : rsStruct.rgss8FixVal[0].back() + 1; + rsStruct.rgss8FixVal[1] += rsStruct.rgss8FixVal[1].empty() ? 'x' : rsStruct.rgss8FixVal[1].front() + 1; + rsStruct.rgss16FixVal[0] += rsStruct.rgss16FixVal[0].empty() ? u'x' : rsStruct.rgss16FixVal[0].back() + 1; + rsStruct.rgss16FixVal[1] += rsStruct.rgss16FixVal[1].empty() ? u'x' : rsStruct.rgss16FixVal[1].front() + 1; + rsStruct.rgss32FixVal[0] += rsStruct.rgss32FixVal[0].empty() ? U'x' : rsStruct.rgss32FixVal[0].back() + 1; + rsStruct.rgss32FixVal[1] += rsStruct.rgss32FixVal[1].empty() ? U'x' : rsStruct.rgss32FixVal[1].front() + 1; + rsStruct.rgwssFixVal[0] += rsStruct.rgwssFixVal[0].empty() ? L'x' : rsStruct.rgwssFixVal[0].back() + 1; + rsStruct.rgwssFixVal[1] += rsStruct.rgwssFixVal[1].empty() ? L'x' : rsStruct.rgwssFixVal[1].front() + 1; + rsStruct.rgptr8Val[0].resize(rsStruct.rgptr8Val[0].size() * 2); + rsStruct.rgptr8Val[1].resize(rsStruct.rgptr8Val[1].size() * 3); + for (size_t nIndex = 0; nIndex < rsStruct.rgptr8Val[0].size() / 2; nIndex++) + rsStruct.rgptr8Val[0][nIndex + rsStruct.rgptr8Val[0].size() / 2] = rsStruct.rgptr8Val[0][nIndex]; + for (size_t nIndex = 0; nIndex < rsStruct.rgptr8Val[1].size() / 3; nIndex++) + rsStruct.rgptr8Val[1][nIndex + rsStruct.rgptr8Val[1].size() / 3] = rsStruct.rgptr8Val[1][nIndex]; + rsStruct.rgptrssVal[0].resize(rsStruct.rgptrssVal[0].size() * 2); + rsStruct.rgptrssVal[1].resize(rsStruct.rgptrssVal[1].size() * 3); + for (size_t nIndex = 0; nIndex < rsStruct.rgptrssVal[0].size() / 2; nIndex++) + rsStruct.rgptrssVal[0][nIndex + rsStruct.rgptrssVal[0].size() / 2] = rsStruct.rgptrssVal[0][nIndex]; + for (size_t nIndex = 0; nIndex < rsStruct.rgptrssVal[1].size() / 3; nIndex++) + rsStruct.rgptrssVal[1][nIndex + rsStruct.rgptrssVal[1].size() / 3] = rsStruct.rgptrssVal[1][nIndex]; + for (size_t nIndex = 0; nIndex < rsStruct.rgptr8FixVal[0].size(); nIndex++) + rsStruct.rgptr8FixVal[0][nIndex] = static_cast(rsStruct.rgptr8FixVal[0][nIndex] << 1); + for (size_t nIndex = 0; nIndex < rsStruct.rgptr8FixVal[1].size(); nIndex++) + rsStruct.rgptr8FixVal[1][nIndex] = static_cast(rsStruct.rgptr8FixVal[1][nIndex] << 1); + for (size_t nIndex = 0; nIndex < rsStruct.rgptrssFixVal[0].size(); nIndex++) + rsStruct.rgptrssFixVal[0][nIndex] = rsStruct.rgptrssFixVal[0][nIndex] + rsStruct.rgptrssFixVal[0][nIndex]; + for (size_t nIndex = 0; nIndex < rsStruct.rgptrssFixVal[1].size(); nIndex++) + rsStruct.rgptrssFixVal[1][nIndex] = rsStruct.rgptrssFixVal[1][nIndex] + rsStruct.rgptrssFixVal[1][nIndex]; + rsStruct.rgseq8Val[0].resize(rsStruct.rgseq8Val[0].size() * 2); + rsStruct.rgseq8Val[1].resize(rsStruct.rgseq8Val[1].size() * 3); + for (size_t nIndex = 0; nIndex < rsStruct.rgseq8Val[0].size() / 2; nIndex++) + rsStruct.rgseq8Val[0][nIndex + rsStruct.rgseq8Val[0].size() / 2] = rsStruct.rgseq8Val[0][nIndex]; + for (size_t nIndex = 0; nIndex < rsStruct.rgseq8Val[1].size() / 3; nIndex++) + rsStruct.rgseq8Val[1][nIndex + rsStruct.rgseq8Val[1].size() / 3] = rsStruct.rgseq8Val[1][nIndex]; + rsStruct.rgseqssVal[0].resize(rsStruct.rgseqssVal[0].size() * 2); + rsStruct.rgseqssVal[1].resize(rsStruct.rgseqssVal[1].size() * 3); + for (size_t nIndex = 0; nIndex < rsStruct.rgseqssVal[0].size() / 2; nIndex++) + rsStruct.rgseqssVal[0][nIndex + rsStruct.rgseqssVal[0].size() / 2] = rsStruct.rgseqssVal[0][nIndex]; + for (size_t nIndex = 0; nIndex < rsStruct.rgseqssVal[1].size() / 3; nIndex++) + rsStruct.rgseqssVal[1][nIndex + rsStruct.rgseqssVal[1].size() / 3] = rsStruct.rgseqssVal[1][nIndex]; + for (size_t nIndex = 0; nIndex < rsStruct.rgseq8FixVal[0].size(); nIndex++) + rsStruct.rgseq8FixVal[0][nIndex] = static_cast(rsStruct.rgseq8FixVal[0][nIndex] << 1); + for (size_t nIndex = 0; nIndex < rsStruct.rgseq8FixVal[1].size(); nIndex++) + rsStruct.rgseq8FixVal[1][nIndex] = static_cast(rsStruct.rgseq8FixVal[1][nIndex] << 1); + for (size_t nIndex = 0; nIndex < rsStruct.rgseqssFixVal[0].size(); nIndex++) + rsStruct.rgseqssFixVal[0][nIndex] = rsStruct.rgseqssFixVal[0][nIndex] + rsStruct.rgseqssFixVal[0][nIndex]; + for (size_t nIndex = 0; nIndex < rsStruct.rgseqssFixVal[1].size(); nIndex++) + rsStruct.rgseqssFixVal[1][nIndex] = rsStruct.rgseqssFixVal[1][nIndex] + rsStruct.rgseqssFixVal[1][nIndex]; + // Incompatible serdes generation: BUG #398509 + // if (rsStruct.rgifcVal[0].get() && rsStruct.rgifcVal[0].id() == sdv::GetInterfaceId()) + //{ + // m_summer.set(rsStruct.rgifcVal[0].get()); + // rsStruct.rgifcVal[0] = static_cast(&m_summer); + //} + // else + // rsStruct.rgifcVal[0] = nullptr; + // if (rsStruct.rgifcVal[1].get() && rsStruct.rgifcVal[1].id() == sdv::GetInterfaceId()) + //{ + // m_summer.set(rsStruct.rgifcVal[1].get()); + // rsStruct.rgifcVal[1] = static_cast(&m_summer); + //} + // else + // rsStruct.rgifcVal[1] = nullptr; + // if (rsStruct.rgpMultiplyValue[0]) + //{ + // m_multiplier.set(rsStruct.rgpMultiplyValue[0]); + // rsStruct.rgpMultiplyValue[0] = static_cast(&m_multiplier); + //} + // else + // rsStruct.rgpMultiplyValue[0] = nullptr; + // if (rsStruct.rgpMultiplyValue[1]) + //{ + // m_multiplier.set(rsStruct.rgpMultiplyValue[1]); + // rsStruct.rgpMultiplyValue[1] = static_cast(&m_multiplier); + //} + // else + // rsStruct.rgpMultiplyValue[1] = nullptr; + rsStruct.rgsSubVal[0].i += 1; + rsStruct.rgsSubVal[1].i += 2; + // Incompatible serdes generation: BUG #398509 + // rsStruct.rgsIndVal[0].i += 10; + // rsStruct.rgsIndVal[1].i += 20; + // Unnamed struct with array is not yet supported due to incorret serdes code generation: BUG #398246 + // rsStruct.rgsUnnamedVal[0].i += 20; + // rsStruct.rgsUnnamedVal[1].i += 40; + + rsStruct.tbVal = !rsStruct.tbVal; + rsStruct.tsVal--; + rsStruct.tusVal++; + rsStruct.tlVal--; + rsStruct.tulVal++; + rsStruct.tllVal--; + rsStruct.tullVal++; + rsStruct.ti8Val--; + rsStruct.tui8Val++; + rsStruct.ti16Val--; + rsStruct.tui16Val++; + rsStruct.ti32Val--; + rsStruct.tui32Val++; + rsStruct.ti64Val--; + rsStruct.tui64Val++; + rsStruct.tcVal++; + rsStruct.tc16Val++; + rsStruct.tc32Val++; + rsStruct.twcVal++; + rsStruct.tfVal -= 1000.000f; + rsStruct.tdVal += 1000.000; + rsStruct.tldVal -= 1000.000; + rsStruct.tnVal++; + rsStruct.teHelloVal = rsStruct.teHelloVal++; + if (rsStruct.tidVal == sdv::GetInterfaceId()) + rsStruct.tidVal = sdv::GetInterfaceId(); + else + rsStruct.tidVal = 0; + rsStruct.tssVal += rsStruct.tssVal.empty() ? 'x' : rsStruct.tssVal.back() + 1; + rsStruct.tss8Val += rsStruct.tss8Val.empty() ? 'x' : rsStruct.tss8Val.back() + 1; + rsStruct.tss16Val += rsStruct.tss16Val.empty() ? u'x' : rsStruct.tss16Val.back() + 1; + rsStruct.tss32Val += rsStruct.tss32Val.empty() ? U'x' : rsStruct.tss32Val.back() + 1; + rsStruct.twssVal += rsStruct.twssVal.empty() ? L'x' : rsStruct.twssVal.back() + 1; + rsStruct.tssFixVal += rsStruct.tssFixVal.empty() ? 'x' : rsStruct.tssFixVal.back() + 1; + rsStruct.tss8FixVal += rsStruct.tss8FixVal.empty() ? 'x' : rsStruct.tss8FixVal.back() + 1; + rsStruct.tss16FixVal += rsStruct.tss16FixVal.empty() ? u'x' : rsStruct.tss16FixVal.back() + 1; + rsStruct.tss32FixVal += rsStruct.tss32FixVal.empty() ? U'x' : rsStruct.tss32FixVal.back() + 1; + rsStruct.twssFixVal += rsStruct.twssFixVal.empty() ? L'x' : rsStruct.twssFixVal.back() + 1; + // Using typedefs of template based types produced an error: BUG #398266 + // rsStruct.tptr8Val.resize(rsStruct.tptr8Val.size() * 2); + // for (size_t nIndex = 0; nIndex < rsStruct.tptr8Val.size() / 2; nIndex++) + // rsStruct.tptr8Val[nIndex + rsStruct.tptr8Val.size() / 2] = rsStruct.tptr8Val[nIndex]; + // rsStruct.ptrssVal.resize(rsStruct.tptrssVal.size() * 2); + // for (size_t nIndex = 0; nIndex < rsStruct.tptrssVal.size() / 2; nIndex++) + // rsStruct.tptrssVal[nIndex + rsStruct.tptrssVal.size() / 2] = rsStruct.tptrssVal[nIndex]; + // for (size_t nIndex = 0; nIndex < rsStruct.tptr8FixVal.size(); nIndex++) + // rsStruct.tptr8FixVal[nIndex] = static_cast(rsStruct.tptr8FixVal[nIndex] << 1); + // for (size_t nIndex = 0; nIndex < rsStruct.tptrssFixVal.size(); nIndex++) + // rsStruct.ptrssFixVal[nIndex] = rsStruct.tptrssFixVal[nIndex] + rsStruct.tptrssFixVal[nIndex]; + // rsStruct.tseq8Val.resize(rsStruct.tseq8Val.size() * 2); + // for (size_t nIndex = 0; nIndex < rsStruct.tseq8Val.size() / 2; nIndex++) + // rsStruct.tseq8Val[nIndex + rsStruct.tseq8Val.size() / 2] = rsStruct.tseq8Val[nIndex]; + // rsStruct.tseqssVal.resize(rsStruct.tseqssVal.size() * 2); + // for (size_t nIndex = 0; nIndex < rsStruct.tseqssVal.size() / 2; nIndex++) + // rsStruct.seqssVal[nIndex + rsStruct.tseqssVal.size() / 2] = rsStruct.tseqssVal[nIndex]; + // for (size_t nIndex = 0; nIndex < rsStruct.tseq8FixVal.size(); nIndex++) + // rsStruct.tseq8FixVal[nIndex] = static_cast(rsStruct.tseq8FixVal[nIndex] << 1); + // for (size_t nIndex = 0; nIndex < rsStruct.tseqssFixVal.size(); nIndex++) + // rsStruct.tseqssFixVal[nIndex] = rsStruct.tseqssFixVal[nIndex] + rsStruct.tseqssFixVal[nIndex]; + if (rsStruct.tifcVal.get() && rsStruct.tifcVal.id() == sdv::GetInterfaceId()) + { + m_summerTypedef.set(rsStruct.tifcVal.get()); + rsStruct.tifcVal = static_cast(&m_summerTypedef); + } + else + rsStruct.tifcVal = nullptr; + // Typedef of interfaces current not possible. BUG #399464 + // if (rsStruct.tpMultiplyValue) + //{ + // m_multiplierTypedef.set(rsStruct.tpMultiplyValue); + // rsStruct.tpMultiplyValue = static_cast(&m_multiplierTypedef); + //} + // else + // rsStruct.tpMultiplyValue = nullptr; + rsStruct.tsSubVal.i += 1; + rsStruct.tsIndVal.i += 10; + // Incompatible serdes generation: BUG #398509 + // rsStruct.tsUnnamedVal.i += 20; + // Unnamed unions (inline definition) are not supported yet: PBI #398209 + // union switch(int32) { case 0: int32 i; default: int32 j; } tuUnnamedVal; + // Anonymous structs and unions (unnamed and no declaration) are not supported yet: PBI #397894 + // struct { int32 iAnonymous1; int32 iAnonymous2; }; + // union switch(int32) { case 0: int32 iAnonymous1; default: int32 iAnonymous2; }; + } + + /** + * @brief Update the mega union. This will test marshalling and unmarshalling of the mega union. Overload of + * IMegaTest::ProcessMegaTypeBasedUnion. + */ + virtual void ProcessMegaTypeBasedUnion(/*inout*/ UMegaTypeBasedUnion& ruUnion) override + { + switch (ruUnion.get_switch()) + { + case 1: + ruUnion.bVal = !ruUnion.bVal; + break; + case 2: + ruUnion.sVal--; + break; + case 3: + ruUnion.usVal++; + break; + case 4: + ruUnion.lVal--; + break; + case 5: + ruUnion.ulVal++; + break; + case 6: + ruUnion.llVal--; + break; + case 7: + ruUnion.ullVal++; + break; + case 8: + ruUnion.i8Val--; + break; + case 9: + ruUnion.ui8Val++; + break; + case 10: + ruUnion.i16Val--; + break; + case 11: + ruUnion.ui16Val++; + break; + case 12: + ruUnion.i32Val--; + break; + case 13: + ruUnion.ui32Val++; + break; + case 14: + ruUnion.i64Val--; + break; + case 15: + ruUnion.ui64Val++; + break; + case 16: + ruUnion.cVal++; + break; + case 17: + ruUnion.c16Val++; + break; + case 18: + ruUnion.c32Val++; + break; + case 19: + ruUnion.wcVal++; + break; + case 20: + ruUnion.fVal -= 1000.000f; + break; + case 21: + ruUnion.dVal += 1000.000; + break; + // GCC issue with generated initialization in the consructor for the "long double" type: BUG #3982727 + // case 22: ruUnion.ldVal -= 1000.000; break; + case 23: + ruUnion.nVal++; + break; + case 24: + ruUnion.eHelloVal = ruUnion.eHelloVal++; + break; + case 25: + if (ruUnion.idVal == sdv::GetInterfaceId()) + ruUnion.idVal = sdv::GetInterfaceId(); + else + ruUnion.idVal = 0; + break; + case 26: + ruUnion.ssVal += ruUnion.ssVal.empty() ? 'x' : ruUnion.ssVal.back() + 1; + break; + case 27: + ruUnion.ss8Val += ruUnion.ss8Val.empty() ? 'x' : ruUnion.ss8Val.back() + 1; + break; + case 28: + ruUnion.ss16Val += ruUnion.ss16Val.empty() ? u'x' : ruUnion.ss16Val.back() + 1; + break; + case 29: + ruUnion.ss32Val += ruUnion.ss32Val.empty() ? U'x' : ruUnion.ss32Val.back() + 1; + break; + case 30: + ruUnion.wssVal += ruUnion.wssVal.empty() ? L'x' : ruUnion.wssVal.back() + 1; + break; + case 31: + ruUnion.ssFixVal += ruUnion.ssFixVal.empty() ? 'x' : ruUnion.ssFixVal.back() + 1; + break; + case 32: + ruUnion.ss8FixVal += ruUnion.ss8FixVal.empty() ? 'x' : ruUnion.ss8FixVal.back() + 1; + break; + case 33: + ruUnion.ss16FixVal += ruUnion.ss16FixVal.empty() ? u'x' : ruUnion.ss16FixVal.back() + 1; + break; + case 34: + ruUnion.ss32FixVal += ruUnion.ss32FixVal.empty() ? U'x' : ruUnion.ss32FixVal.back() + 1; + break; + case 35: + ruUnion.wssFixVal += ruUnion.wssFixVal.empty() ? L'x' : ruUnion.wssFixVal.back() + 1; + break; + case 36: + ruUnion.ptr8Val.resize(ruUnion.ptr8Val.size() * 2); + for (size_t nIndex = 0; nIndex < ruUnion.ptr8Val.size() / 2; nIndex++) + ruUnion.ptr8Val[nIndex + ruUnion.ptr8Val.size() / 2] = ruUnion.ptr8Val[nIndex]; + break; + case 37: + ruUnion.ptrssVal.resize(ruUnion.ptrssVal.size() * 2); + for (size_t nIndex = 0; nIndex < ruUnion.ptrssVal.size() / 2; nIndex++) + ruUnion.ptrssVal[nIndex + ruUnion.ptrssVal.size() / 2] = ruUnion.ptrssVal[nIndex]; + break; + case 38: + for (size_t nIndex = 0; nIndex < ruUnion.ptr8FixVal.size(); nIndex++) + ruUnion.ptr8FixVal[nIndex] = static_cast(ruUnion.ptr8FixVal[nIndex] << 1); + break; + case 39: + for (size_t nIndex = 0; nIndex < ruUnion.ptrssFixVal.size(); nIndex++) + ruUnion.ptrssFixVal[nIndex] = ruUnion.ptrssFixVal[nIndex] + ruUnion.ptrssFixVal[nIndex]; + break; + case 40: + ruUnion.seq8Val.resize(ruUnion.seq8Val.size() * 2); + for (size_t nIndex = 0; nIndex < ruUnion.seq8Val.size() / 2; nIndex++) + ruUnion.seq8Val[nIndex + ruUnion.seq8Val.size() / 2] = ruUnion.seq8Val[nIndex]; + break; + case 41: + ruUnion.seqssVal.resize(ruUnion.seqssVal.size() * 2); + for (size_t nIndex = 0; nIndex < ruUnion.seqssVal.size() / 2; nIndex++) + ruUnion.seqssVal[nIndex + ruUnion.seqssVal.size() / 2] = ruUnion.seqssVal[nIndex]; + break; + case 42: + for (size_t nIndex = 0; nIndex < ruUnion.seq8FixVal.size(); nIndex++) + ruUnion.seq8FixVal[nIndex] = static_cast(ruUnion.seq8FixVal[nIndex] << 1); + break; + case 43: + for (size_t nIndex = 0; nIndex < ruUnion.seqssFixVal.size(); nIndex++) + ruUnion.seqssFixVal[nIndex] = ruUnion.seqssFixVal[nIndex] + ruUnion.seqssFixVal[nIndex]; + break; + case 44: + if (ruUnion.ifcVal.get() && ruUnion.ifcVal.id() == sdv::GetInterfaceId()) + { + m_summer.set(ruUnion.ifcVal.get()); + ruUnion.ifcVal = static_cast(&m_summer); + } + else + ruUnion.ifcVal = nullptr; + break; + case 45: + if (ruUnion.pMultiplyValue) + { + m_multiplier.set(ruUnion.pMultiplyValue); + ruUnion.pMultiplyValue = static_cast(&m_multiplier); + } + else + ruUnion.pMultiplyValue = nullptr; + break; + case 46: + ruUnion.sSubVal.i += 1; + break; + // Incompatible serdes generation: BUG #398509 + // case 47: ruUnion.sIndVal.i += 10; break; + // Unnamed struct inside unions are not supported yet: PBI #398230 + // case 48: ruUnion.sUnnamedVal.i += 20; break; + // Unnamed unions are not supported yet: PBI #398209 + // case 49: union switch(int32) { case 0: int32 i; default: int32 j; } uUnnamedVal; + // Anonymous structs and unions (unnamed and no declaration) are not supported yet: PBI #397894 + // case 50: struct { int32 iAnonymous1; int32 iAnonymous2; }; + // case 51: union switch(int32) { case 0: int32 iAnonymous1; default: int32 iAnonymous2; }; + + case 201: + ruUnion.tbVal = !ruUnion.tbVal; + break; + case 202: + ruUnion.tsVal--; + break; + case 203: + ruUnion.tusVal++; + break; + case 204: + ruUnion.tlVal--; + break; + case 205: + ruUnion.tulVal++; + break; + case 206: + ruUnion.tllVal--; + break; + case 207: + ruUnion.tullVal++; + break; + case 208: + ruUnion.ti8Val--; + break; + case 209: + ruUnion.tui8Val++; + break; + case 210: + ruUnion.ti16Val--; + break; + case 211: + ruUnion.tui16Val++; + break; + case 212: + ruUnion.ti32Val--; + break; + case 213: + ruUnion.tui32Val++; + break; + case 214: + ruUnion.ti64Val--; + break; + case 215: + ruUnion.tui64Val++; + break; + case 216: + ruUnion.tcVal++; + break; + case 217: + ruUnion.tc16Val++; + break; + case 218: + ruUnion.tc32Val++; + break; + case 219: + ruUnion.twcVal++; + break; + case 220: + ruUnion.tfVal -= 1000.000f; + break; + case 221: + ruUnion.tdVal += 1000.000; + break; + // GCC issue with generated initialization in the consructor for the "long double" type: BUG #3982727 + // case 22: ruUnion.tldVal -= 1000.000; break; + case 223: + ruUnion.tnVal++; + break; + case 224: + ruUnion.teHelloVal = ruUnion.teHelloVal++; + break; + case 225: + if (ruUnion.tidVal == sdv::GetInterfaceId()) + ruUnion.tidVal = sdv::GetInterfaceId(); + else + ruUnion.tidVal = 0; + break; + case 226: + ruUnion.tssVal += ruUnion.tssVal.empty() ? 'x' : ruUnion.tssVal.back() + 1; + break; + case 227: + ruUnion.tss8Val += ruUnion.tss8Val.empty() ? 'x' : ruUnion.tss8Val.back() + 1; + break; + case 228: + ruUnion.tss16Val += ruUnion.tss16Val.empty() ? u'x' : ruUnion.tss16Val.back() + 1; + break; + case 229: + ruUnion.tss32Val += ruUnion.tss32Val.empty() ? U'x' : ruUnion.tss32Val.back() + 1; + break; + case 230: + ruUnion.twssVal += ruUnion.twssVal.empty() ? L'x' : ruUnion.twssVal.back() + 1; + break; + case 231: + ruUnion.tssFixVal += ruUnion.tssFixVal.empty() ? 'x' : ruUnion.tssFixVal.back() + 1; + break; + case 232: + ruUnion.tss8FixVal += ruUnion.tss8FixVal.empty() ? 'x' : ruUnion.tss8FixVal.back() + 1; + break; + case 233: + ruUnion.tss16FixVal += ruUnion.tss16FixVal.empty() ? u'x' : ruUnion.tss16FixVal.back() + 1; + break; + case 234: + ruUnion.tss32FixVal += ruUnion.tss32FixVal.empty() ? U'x' : ruUnion.tss32FixVal.back() + 1; + break; + case 235: + ruUnion.twssFixVal += ruUnion.twssFixVal.empty() ? L'x' : ruUnion.twssFixVal.back() + 1; + break; + // Using typedefs of template based types produced an error: BUG #398266 + // case 236: + // ruUnion.tptr8Val.resize(ruUnion.tptr8Val.size() * 2); + // for (size_t nIndex = 0; nIndex < ruUnion.tptr8Val.size() / 2; nIndex++) + // ruUnion.tptr8Val[nIndex + ruUnion.tptr8Val.size() / 2] = ruUnion.tptr8Val[nIndex]; + // break; + // case 237: + // ruUnion.tptrssVal.resize(ruUnion.tptrssVal.size() * 2); + // for (size_t nIndex = 0; nIndex < ruUnion.tptrssVal.size() / 2; nIndex++) + // ruUnion.tptrssVal[nIndex + ruUnion.tptrssVal.size() / 2] = ruUnion.tptrssVal[nIndex]; + // break; + // case 238: + // for (size_t nIndex = 0; nIndex < ruUnion.tptr8FixVal.size(); nIndex++) + // ruUnion.tptr8FixVal[nIndex] = static_cast(ruUnion.tptr8FixVal[nIndex] << 1); + // break; + // case 239: + // for (size_t nIndex = 0; nIndex < ruUnion.tptrssFixVal.size(); nIndex++) + // ruUnion.tptrssFixVal[nIndex] = ruUnion.tptrssFixVal[nIndex] + ruUnion.tptrssFixVal[nIndex]; + // break; + // case 240: + // ruUnion.tseq8Val.resize(ruUnion.tseq8Val.size() * 2); + // for (size_t nIndex = 0; nIndex < ruUnion.tseq8Val.size() / 2; nIndex++) + // ruUnion.tseq8Val[nIndex + ruUnion.tseq8Val.size() / 2] = ruUnion.tseq8Val[nIndex]; + // break; + // case 241: + // ruUnion.tseqssVal.resize(ruUnion.tseqssVal.size() * 2); + // for (size_t nIndex = 0; nIndex < ruUnion.tseqssVal.size() / 2; nIndex++) + // ruUnion.tseqssVal[nIndex + ruUnion.tseqssVal.size() / 2] = ruUnion.tseqssVal[nIndex]; + // break; + // case 242: + // for (size_t nIndex = 0; nIndex < ruUnion.tseq8FixVal.size(); nIndex++) + // ruUnion.tseq8FixVal[nIndex] = static_cast(ruUnion.tseq8FixVal[nIndex] << 1); + // break; + // case 243: + // for (size_t nIndex = 0; nIndex < ruUnion.tseqssFixVal.size(); nIndex++) + // ruUnion.tseqssFixVal[nIndex] = ruUnion.tseqssFixVal[nIndex] + ruUnion.tseqssFixVal[nIndex]; + // break; + case 244: + if (ruUnion.tifcVal.get() && ruUnion.tifcVal.id() == sdv::GetInterfaceId()) + { + m_summerTypedef.set(ruUnion.tifcVal.get()); + ruUnion.tifcVal = static_cast(&m_summerTypedef); + } + else + ruUnion.tifcVal = nullptr; + break; + // Typedef of interfaces current not possible. BUG #399464 + // case 245: + // if (ruUnion.tpMultiplyValue) + // { + // m_multiplierTypedef.set(ruUnion.tpMultiplyValue); + // ruUnion.tpMultiplyValue = static_cast(&m_multiplierTypedef); + // } + // else + // ruUnion.tpMultiplyValue = nullptr; + // break; + case 246: + ruUnion.tsSubVal.i += 1; + break; + // Incompatible serdes generation: BUG #398509 + case 247: + ruUnion.tsIndVal.i += 10; + break; + // Unnamed struct inside unions are not supported yet: PBI #398230 + // case 248: ruUnion.tsUnnamedVal.i += 20; break; + // Unnamed unions are not supported yet: PBI #398209 + // case 249: union switch(int32) { case 0: int32 i; default: int32 j; } uUnnamedVal; + // Anonymous structs and unions (unnamed and no declaration) are not supported yet: PBI #397894 + // case 250: struct { int32 iAnonymous1; int32 iAnonymous2; }; + // case 251: union switch(int32) { case 0: int32 iAnonymous1; default: int32 iAnonymous2; }; + default: + break; + } + } + + /** + * @brief Update the mega union. This will test marshalling and unmarshalling of the mega union. Overload of + * IMegaTest::ProcessMegaTypeBasedUnion. + */ + virtual void ProcessMegaVarBasedUnion(/*inout*/ SMegaVarBasedUnion& rsUnion) override + { + switch (rsUnion.get_switch()) + { + case 1: + rsUnion.uVal.bVal = !rsUnion.uVal.bVal; + break; + case 2: + rsUnion.uVal.sVal--; + break; + case 3: + rsUnion.uVal.usVal++; + break; + case 4: + rsUnion.uVal.lVal--; + break; + case 5: + rsUnion.uVal.ulVal++; + break; + case 6: + rsUnion.uVal.llVal--; + break; + case 7: + rsUnion.uVal.ullVal++; + break; + case 8: + rsUnion.uVal.i8Val--; + break; + case 9: + rsUnion.uVal.ui8Val++; + break; + case 10: + rsUnion.uVal.i16Val--; + break; + case 11: + rsUnion.uVal.ui16Val++; + break; + case 12: + rsUnion.uVal.i32Val--; + break; + case 13: + rsUnion.uVal.ui32Val++; + break; + case 14: + rsUnion.uVal.i64Val--; + break; + case 15: + rsUnion.uVal.ui64Val++; + break; + case 16: + rsUnion.uVal.cVal++; + break; + case 17: + rsUnion.uVal.c16Val++; + break; + case 18: + rsUnion.uVal.c32Val++; + break; + case 19: + rsUnion.uVal.wcVal++; + break; + case 20: + rsUnion.uVal.fVal -= 1000.000f; + break; + case 21: + rsUnion.uVal.dVal += 1000.000; + break; + // GCC issue with generated initialization in the consructor for the "long double" type: BUG #3982727 + // case 22: rsUnion.uVal.ldVal -= 1000.000; break; + case 23: + rsUnion.uVal.nVal++; + break; + case 24: + rsUnion.uVal.eHelloVal = rsUnion.uVal.eHelloVal++; + break; + case 25: + if (rsUnion.uVal.idVal == sdv::GetInterfaceId()) + rsUnion.uVal.idVal = sdv::GetInterfaceId(); + else + rsUnion.uVal.idVal = 0; + break; + case 26: + rsUnion.uVal.ssVal += rsUnion.uVal.ssVal.empty() ? 'x' : rsUnion.uVal.ssVal.back() + 1; + break; + case 27: + rsUnion.uVal.ss8Val += rsUnion.uVal.ss8Val.empty() ? 'x' : rsUnion.uVal.ss8Val.back() + 1; + break; + case 28: + rsUnion.uVal.ss16Val += rsUnion.uVal.ss16Val.empty() ? u'x' : rsUnion.uVal.ss16Val.back() + 1; + break; + case 29: + rsUnion.uVal.ss32Val += rsUnion.uVal.ss32Val.empty() ? U'x' : rsUnion.uVal.ss32Val.back() + 1; + break; + case 30: + rsUnion.uVal.wssVal += rsUnion.uVal.wssVal.empty() ? L'x' : rsUnion.uVal.wssVal.back() + 1; + break; + case 31: + rsUnion.uVal.ssFixVal += rsUnion.uVal.ssFixVal.empty() ? 'x' : rsUnion.uVal.ssFixVal.back() + 1; + break; + case 32: + rsUnion.uVal.ss8FixVal += rsUnion.uVal.ss8FixVal.empty() ? 'x' : rsUnion.uVal.ss8FixVal.back() + 1; + break; + case 33: + rsUnion.uVal.ss16FixVal += rsUnion.uVal.ss16FixVal.empty() ? u'x' : rsUnion.uVal.ss16FixVal.back() + 1; + break; + case 34: + rsUnion.uVal.ss32FixVal += rsUnion.uVal.ss32FixVal.empty() ? U'x' : rsUnion.uVal.ss32FixVal.back() + 1; + break; + case 35: + rsUnion.uVal.wssFixVal += rsUnion.uVal.wssFixVal.empty() ? L'x' : rsUnion.uVal.wssFixVal.back() + 1; + break; + case 36: + rsUnion.uVal.ptr8Val.resize(rsUnion.uVal.ptr8Val.size() * 2); + for (size_t nIndex = 0; nIndex < rsUnion.uVal.ptr8Val.size() / 2; nIndex++) + rsUnion.uVal.ptr8Val[nIndex + rsUnion.uVal.ptr8Val.size() / 2] = rsUnion.uVal.ptr8Val[nIndex]; + break; + case 37: + rsUnion.uVal.ptrssVal.resize(rsUnion.uVal.ptrssVal.size() * 2); + for (size_t nIndex = 0; nIndex < rsUnion.uVal.ptrssVal.size() / 2; nIndex++) + rsUnion.uVal.ptrssVal[nIndex + rsUnion.uVal.ptrssVal.size() / 2] = rsUnion.uVal.ptrssVal[nIndex]; + break; + case 38: + for (size_t nIndex = 0; nIndex < rsUnion.uVal.ptr8FixVal.size(); nIndex++) + rsUnion.uVal.ptr8FixVal[nIndex] = static_cast(rsUnion.uVal.ptr8FixVal[nIndex] << 1); + break; + case 39: + for (size_t nIndex = 0; nIndex < rsUnion.uVal.ptrssFixVal.size(); nIndex++) + rsUnion.uVal.ptrssFixVal[nIndex] = rsUnion.uVal.ptrssFixVal[nIndex] + rsUnion.uVal.ptrssFixVal[nIndex]; + break; + case 40: + rsUnion.uVal.seq8Val.resize(rsUnion.uVal.seq8Val.size() * 2); + for (size_t nIndex = 0; nIndex < rsUnion.uVal.seq8Val.size() / 2; nIndex++) + rsUnion.uVal.seq8Val[nIndex + rsUnion.uVal.seq8Val.size() / 2] = rsUnion.uVal.seq8Val[nIndex]; + break; + case 41: + rsUnion.uVal.seqssVal.resize(rsUnion.uVal.seqssVal.size() * 2); + for (size_t nIndex = 0; nIndex < rsUnion.uVal.seqssVal.size() / 2; nIndex++) + rsUnion.uVal.seqssVal[nIndex + rsUnion.uVal.seqssVal.size() / 2] = rsUnion.uVal.seqssVal[nIndex]; + break; + case 42: + for (size_t nIndex = 0; nIndex < rsUnion.uVal.seq8FixVal.size(); nIndex++) + rsUnion.uVal.seq8FixVal[nIndex] = static_cast(rsUnion.uVal.seq8FixVal[nIndex] << 1); + break; + case 43: + for (size_t nIndex = 0; nIndex < rsUnion.uVal.seqssFixVal.size(); nIndex++) + rsUnion.uVal.seqssFixVal[nIndex] = rsUnion.uVal.seqssFixVal[nIndex] + rsUnion.uVal.seqssFixVal[nIndex]; + break; + case 44: + if (rsUnion.uVal.ifcVal.get() && rsUnion.uVal.ifcVal.id() == sdv::GetInterfaceId()) + { + m_summer.set(rsUnion.uVal.ifcVal.get()); + rsUnion.uVal.ifcVal = static_cast(&m_summer); + } + else + rsUnion.uVal.ifcVal = nullptr; + break; + case 45: + if (rsUnion.uVal.pMultiplyValue) + { + m_multiplier.set(rsUnion.uVal.pMultiplyValue); + rsUnion.uVal.pMultiplyValue = static_cast(&m_multiplier); + } + else + rsUnion.uVal.pMultiplyValue = nullptr; + break; + case 46: + rsUnion.uVal.sSubVal.i += 1; + break; + // Incompatible serdes generation: BUG #398509 + // case 47: rsUnion.uVal.sIndVal.i += 10; break; + // Unnamed struct inside unions are not supported yet: PBI #398230 + // case 48: rsUnion.uVal.sUnnamedVal.i += 20; break; + // Unnamed unions are not supported yet: PBI #398209 + // case 49: union switch(int32) { case 0: int32 i; default: int32 j; } uUnnamedVal; + // Anonymous structs and unions (unnamed and no declaration) are not supported yet: PBI #397894 + // case 50: struct { int32 iAnonymous1; int32 iAnonymous2; }; + // case 51: union switch(int32) { case 0: int32 iAnonymous1; default: int32 iAnonymous2; }; + + case 201: + rsUnion.uVal.tbVal = !rsUnion.uVal.tbVal; + break; + case 202: + rsUnion.uVal.tsVal--; + break; + case 203: + rsUnion.uVal.tusVal++; + break; + case 204: + rsUnion.uVal.tlVal--; + break; + case 205: + rsUnion.uVal.tulVal++; + break; + case 206: + rsUnion.uVal.tllVal--; + break; + case 207: + rsUnion.uVal.tullVal++; + break; + case 208: + rsUnion.uVal.ti8Val--; + break; + case 209: + rsUnion.uVal.tui8Val++; + break; + case 210: + rsUnion.uVal.ti16Val--; + break; + case 211: + rsUnion.uVal.tui16Val++; + break; + case 212: + rsUnion.uVal.ti32Val--; + break; + case 213: + rsUnion.uVal.tui32Val++; + break; + case 214: + rsUnion.uVal.ti64Val--; + break; + case 215: + rsUnion.uVal.tui64Val++; + break; + case 216: + rsUnion.uVal.tcVal++; + break; + case 217: + rsUnion.uVal.tc16Val++; + break; + case 218: + rsUnion.uVal.tc32Val++; + break; + case 219: + rsUnion.uVal.twcVal++; + break; + case 220: + rsUnion.uVal.tfVal -= 1000.000f; + break; + case 221: + rsUnion.uVal.tdVal += 1000.000; + break; + // GCC issue with generated initialization in the consructor for the "long double" type: BUG #3982727 + // case 22: rsUnion.uVal.tldVal -= 1000.000; break; + case 223: + rsUnion.uVal.tnVal++; + break; + case 224: + rsUnion.uVal.teHelloVal = rsUnion.uVal.teHelloVal++; + break; + case 225: + if (rsUnion.uVal.tidVal == sdv::GetInterfaceId()) + rsUnion.uVal.tidVal = sdv::GetInterfaceId(); + else + rsUnion.uVal.tidVal = 0; + break; + case 226: + rsUnion.uVal.tssVal += rsUnion.uVal.tssVal.empty() ? 'x' : rsUnion.uVal.tssVal.back() + 1; + break; + case 227: + rsUnion.uVal.tss8Val += rsUnion.uVal.tss8Val.empty() ? 'x' : rsUnion.uVal.tss8Val.back() + 1; + break; + case 228: + rsUnion.uVal.tss16Val += rsUnion.uVal.tss16Val.empty() ? u'x' : rsUnion.uVal.tss16Val.back() + 1; + break; + case 229: + rsUnion.uVal.tss32Val += rsUnion.uVal.tss32Val.empty() ? U'x' : rsUnion.uVal.tss32Val.back() + 1; + break; + case 230: + rsUnion.uVal.twssVal += rsUnion.uVal.twssVal.empty() ? L'x' : rsUnion.uVal.twssVal.back() + 1; + break; + case 231: + rsUnion.uVal.tssFixVal += rsUnion.uVal.tssFixVal.empty() ? 'x' : rsUnion.uVal.tssFixVal.back() + 1; + break; + case 232: + rsUnion.uVal.tss8FixVal += rsUnion.uVal.tss8FixVal.empty() ? 'x' : rsUnion.uVal.tss8FixVal.back() + 1; + break; + case 233: + rsUnion.uVal.tss16FixVal += rsUnion.uVal.tss16FixVal.empty() ? u'x' : rsUnion.uVal.tss16FixVal.back() + 1; + break; + case 234: + rsUnion.uVal.tss32FixVal += rsUnion.uVal.tss32FixVal.empty() ? U'x' : rsUnion.uVal.tss32FixVal.back() + 1; + break; + case 235: + rsUnion.uVal.twssFixVal += rsUnion.uVal.twssFixVal.empty() ? L'x' : rsUnion.uVal.twssFixVal.back() + 1; + break; + // Using typedefs of template based types produced an error: BUG #398266 + // case 236: + // rsUnion.uVal.tptr8Val.resize(rsUnion.uVal.tptr8Val.size() * 2); + // for (size_t nIndex = 0; nIndex < rsUnion.uVal.tptr8Val.size() / 2; nIndex++) + // rsUnion.uVal.tptr8Val[nIndex + rsUnion.uVal.tptr8Val.size() / 2] = rsUnion.uVal.tptr8Val[nIndex]; + // break; + // case 237: + // rsUnion.uVal.tptrssVal.resize(rsUnion.uVal.tptrssVal.size() * 2); + // for (size_t nIndex = 0; nIndex < rsUnion.uVal.tptrssVal.size() / 2; nIndex++) + // rsUnion.uVal.tptrssVal[nIndex + rsUnion.uVal.tptrssVal.size() / 2] = rsUnion.uVal.tptrssVal[nIndex]; + // break; + // case 238: + // for (size_t nIndex = 0; nIndex < rsUnion.uVal.tptr8FixVal.size(); nIndex++) + // rsUnion.uVal.tptr8FixVal[nIndex] = static_cast(rsUnion.uVal.tptr8FixVal[nIndex] << 1); + // break; + // case 239: + // for (size_t nIndex = 0; nIndex < rsUnion.uVal.tptrssFixVal.size(); nIndex++) + // rsUnion.uVal.tptrssFixVal[nIndex] = rsUnion.uVal.tptrssFixVal[nIndex] + rsUnion.uVal.tptrssFixVal[nIndex]; + // break; + // case 240: + // rsUnion.uVal.tseq8Val.resize(rsUnion.uVal.tseq8Val.size() * 2); + // for (size_t nIndex = 0; nIndex < rsUnion.uVal.tseq8Val.size() / 2; nIndex++) + // rsUnion.uVal.tseq8Val[nIndex + rsUnion.uVal.tseq8Val.size() / 2] = rsUnion.uVal.tseq8Val[nIndex]; + // break; + // case 241: + // rsUnion.uVal.tseqssVal.resize(rsUnion.uVal.tseqssVal.size() * 2); + // for (size_t nIndex = 0; nIndex < rsUnion.uVal.tseqssVal.size() / 2; nIndex++) + // rsUnion.uVal.tseqssVal[nIndex + rsUnion.uVal.tseqssVal.size() / 2] = rsUnion.uVal.tseqssVal[nIndex]; + // break; + // case 242: + // for (size_t nIndex = 0; nIndex < rsUnion.uVal.tseq8FixVal.size(); nIndex++) + // rsUnion.uVal.tseq8FixVal[nIndex] = static_cast(rsUnion.uVal.tseq8FixVal[nIndex] << 1); + // break; + // case 243: + // for (size_t nIndex = 0; nIndex < rsUnion.uVal.tseqssFixVal.size(); nIndex++) + // rsUnion.uVal.tseqssFixVal[nIndex] = rsUnion.uVal.tseqssFixVal[nIndex] + rsUnion.uVal.tseqssFixVal[nIndex]; + // break; + case 244: + if (rsUnion.uVal.tifcVal.get() && rsUnion.uVal.tifcVal.id() == sdv::GetInterfaceId()) + { + m_summerTypedef.set(rsUnion.uVal.tifcVal.get()); + rsUnion.uVal.tifcVal = static_cast(&m_summerTypedef); + } + else + rsUnion.uVal.tifcVal = nullptr; + break; + // Typedef of interfaces current not possible. BUG #399464 + // case 245: + // if (rsUnion.uVal.tpMultiplyValue) + // { + // m_multiplierTypedef.set(rsUnion.uVal.tpMultiplyValue); + // rsUnion.uVal.tpMultiplyValue = static_cast(&m_multiplierTypedef); + // } + // else + // rsUnion.uVal.tpMultiplyValue = nullptr; + // break; + case 246: + rsUnion.uVal.tsSubVal.i += 1; + break; + // Incompatible serdes generation: BUG #398509 + case 247: + rsUnion.uVal.tsIndVal.i += 10; + break; + // Unnamed struct inside unions are not supported yet: PBI #398230 + // case 248: rsUnion.uVal.tsUnnamedVal.i += 20; break; + // Unnamed unions are not supported yet: PBI #398209 + // case 249: union switch(int32) { case 0: int32 i; default: int32 j; } uUnnamedVal; + // Anonymous structs and unions (unnamed and no declaration) are not supported yet: PBI #397894 + // case 250: struct { int32 iAnonymous1; int32 iAnonymous2; }; + // case 251: union switch(int32) { case 0: int32 iAnonymous1; default: int32 iAnonymous2; }; + default: + break; + } + } + + ISayHello* m_pHello = nullptr; + + struct SInternalMultiply : IMultiplyValue + { + void set(IMultiplyValue* p) + { + pMultiplyValue = p; + } + size_t Multiply(size_t n) + { + if (!pMultiplyValue) + return 0; + return pMultiplyValue->Multiply(n) * pMultiplyValue->Multiply(n); + } + IMultiplyValue* pMultiplyValue = nullptr; + }; + SInternalMultiply m_multiplier; + SInternalMultiply m_multiplierTypedef; + + struct SInternalAdd : IAddValue + { + void set(IMultiplyValue* p) + { + pMultiplyValue = p; + } + size_t Add(size_t n) + { + if (!pMultiplyValue) + return 0; + return pMultiplyValue->Multiply(n) + pMultiplyValue->Multiply(n); + } + IMultiplyValue* pMultiplyValue = nullptr; + }; + SInternalAdd m_summer; + SInternalAdd m_summerTypedef; +}; + +enum class ELocalIpcBackend +{ + sharedmem, + sockets, + tunnel +}; + +inline void LoadIPCModules(CCommunicationControl& rControl, ELocalIpcBackend eLocalBackend) +{ + sdv::u8string ssModule, ssObject; + switch (eLocalBackend) + { + case ELocalIpcBackend::sockets: +#if defined(_WIN32) + ssModule = "uds_win_sockets.sdv"; + ssObject = "WinSocketsChannelControl"; +#elif defined(__unix__) + ssModule = "uds_unix_sockets.sdv"; + ssObject = "UnixSocketsChannelControl"; +#else + #error Unsupported platform for IPC communication unit tests +#endif + break; + case ELocalIpcBackend::tunnel: +#if defined(_WIN32) + ssModule = "uds_win_tunnel.sdv"; + ssObject = "WinTunnelChannelControl"; +#elif defined(__unix__) + ssModule = "uds_unix_tunnel.sdv"; + ssObject = "UnixTunnelChannelControl"; +#else + #error Unsupported platform for IPC communication unit tests +#endif + break; + case ELocalIpcBackend::sharedmem: + default: + ssModule = "ipc_shared_mem.sdv"; + ssObject = "DefaultSharedMemory"; + break; + } + + // Load the IPC modules + sdv::core::IModuleControl* pModuleControl = sdv::core::GetObject("ModuleControlService"); + ASSERT_NE(pModuleControl, nullptr); + EXPECT_NE(pModuleControl->Load(ssModule), 0u); + EXPECT_NE(pModuleControl->Load("core_ps.sdv"), 0u); + EXPECT_NE(pModuleControl->Load("UnitTest_IPC_Communication_ps.sdv"), 0u); + + // Register communication control + sdv::core::IRegisterForeignObject* pRegisterObject = sdv::core::GetObject("RepositoryService"); + + ASSERT_NE(pRegisterObject, nullptr); + + EXPECT_NE(pRegisterObject->RegisterObject(&rControl, "CommunicationControl"), 0u); + + // Create channel control + sdv::core::IRepositoryControl* pRepositoryControl = sdv::core::GetObject("RepositoryService"); + + ASSERT_NE(pRepositoryControl, nullptr); + + if (eLocalBackend == ELocalIpcBackend::tunnel) + { + // CRITICAL: channel name MUST match CreateEndpoint("DEFAULT_CHANNEL") + EXPECT_NE(pRepositoryControl->CreateObject(ssObject, "DEFAULT_CHANNEL", ""), 0u); + } + else + { + // sockets + sharedmem don't require explicit channel naming + EXPECT_NE(pRepositoryControl->CreateObject(ssObject, "", ""), 0u); + } +} diff --git a/tests/unit_tests/ipc_com/ipc_com.cpp b/tests/unit_tests/ipc_com/ipc_com_sharedmem.cpp similarity index 73% rename from tests/unit_tests/ipc_com/ipc_com.cpp rename to tests/unit_tests/ipc_com/ipc_com_sharedmem.cpp index 8c2e643..1f5aaa5 100644 --- a/tests/unit_tests/ipc_com/ipc_com.cpp +++ b/tests/unit_tests/ipc_com/ipc_com_sharedmem.cpp @@ -11,934 +11,18 @@ * Erik Verhoeven - initial API and implementation ********************************************************************************/ -#include "../../include/gtest_custom.h" -#include "../../../sdv_services/ipc_com/com_ctrl.h" -#include "../../../sdv_services/ipc_com/com_channel.h" -#include "../../../sdv_services/ipc_com/marshall_object.h" -#include -#include -#include -#include "generated/test_ifc.h" +#include "include.h" -EHello& operator++(EHello& reHello, int) -{ - switch (reHello) - { - case EHello::hi: reHello = EHello::huhu; break; - case EHello::huhu: reHello = EHello::hello; break; - case EHello::hello: reHello = EHello::hallo; break; - case EHello::hallo: reHello = EHello::servus; break; - case EHello::servus: reHello = EHello::gruessgott; break; - case EHello::gruessgott: reHello = EHello::hi; break; - default: break; - } - return reHello; -} - -EHello& operator--(EHello& reHello, int) -{ - switch (reHello) - { - case EHello::hi: reHello = EHello::gruessgott; break; - case EHello::huhu: reHello = EHello::hi; break; - case EHello::hello: reHello = EHello::huhu; break; - case EHello::hallo: reHello = EHello::hello; break; - case EHello::servus: reHello = EHello::hallo; break; - case EHello::gruessgott: reHello = EHello::servus; break; - default: break; - } - return reHello; -} - -/** - * - Instantiate service - * - Assign server endpoint - * - Assign server endpoint + assign client endpoint - * - Create channel endpoint - * - Create channel endpoint + connect channel endpoint - * - Create proxy/stub connection - * - Reconnect - * - Multiple connections - * - Get other interface - * - Multiple access interface - * - Random multiple access interface - * - Random multiple access interface with reconnect - */ - -class CInterfaceTest : public ISayHello, public IRequestHello, public IRegisterHelloCallback, public IMegaTest, public sdv::IInterfaceAccess -{ -public: - BEGIN_SDV_INTERFACE_MAP() - SDV_INTERFACE_ENTRY(ISayHello) - SDV_INTERFACE_ENTRY(IRequestHello) - SDV_INTERFACE_ENTRY(IRegisterHelloCallback) - SDV_INTERFACE_ENTRY(IMegaTest) - SDV_INTERFACE_ENTRY(sdv::IInterfaceAccess) - END_SDV_INTERFACE_MAP() - - /** - * @brief Say hello! - * @return The greeting text. - */ - virtual sdv::string Hello() override - { - return "Hello"; - } - - /** - * @brief Request the hello interface. - * @return pHello The hello interface. - */ - virtual ISayHello* Request() - { - return this; - } - - /** - * @brief Register the callback with the hello interface. - * @param[in] pHello The hello interface. - */ - virtual void Register(/*in*/ ISayHello* pHello) - { - m_pHello = pHello; - } - - /** - * @brief Update the mega struct. This will test marshalling and unmarshalling of the mega struct. Overload of - * IMegaTest::ProcessMegaStruct. - * @param[in] rsStruct Reference to the mega struct. - */ - virtual void ProcessMegaStruct(/*inout*/ SMegaStruct& rsStruct) override - { - rsStruct.bVal = !rsStruct.bVal; - rsStruct.sVal--; - rsStruct.usVal++; - rsStruct.lVal--; - rsStruct.ulVal++; - rsStruct.llVal--; - rsStruct.ullVal++; - rsStruct.i8Val--; - rsStruct.ui8Val++; - rsStruct.i16Val--; - rsStruct.ui16Val++; - rsStruct.i32Val--; - rsStruct.ui32Val++; - rsStruct.i64Val--; - rsStruct.ui64Val++; - rsStruct.cVal++; - rsStruct.c16Val++; - rsStruct.c32Val++; - rsStruct.wcVal++; - rsStruct.fVal -= 1000.000f; - rsStruct.dVal += 1000.000; - rsStruct.ldVal -= 1000.000; - rsStruct.nVal++; - rsStruct.eHelloVal = rsStruct.eHelloVal++; - if (rsStruct.idVal == sdv::GetInterfaceId()) - rsStruct.idVal = sdv::GetInterfaceId(); - else - rsStruct.idVal = 0; - rsStruct.ssVal += rsStruct.ssVal.empty() ? 'x' : rsStruct.ssVal.back() + 1; - rsStruct.ss8Val += rsStruct.ss8Val.empty() ? 'x' : rsStruct.ss8Val.back() + 1; - rsStruct.ss16Val += rsStruct.ss16Val.empty() ? u'x' : rsStruct.ss16Val.back() + 1; - rsStruct.ss32Val += rsStruct.ss32Val.empty() ? U'x' : rsStruct.ss32Val.back() + 1; - rsStruct.wssVal += rsStruct.wssVal.empty() ? L'x' : rsStruct.wssVal.back() + 1; - rsStruct.ssFixVal += rsStruct.ssFixVal.empty() ? 'x' : rsStruct.ssFixVal.back() + 1; - rsStruct.ss8FixVal += rsStruct.ss8FixVal.empty() ? 'x' : rsStruct.ss8FixVal.back() + 1; - rsStruct.ss16FixVal += rsStruct.ss16FixVal.empty() ? u'x' : rsStruct.ss16FixVal.back() + 1; - rsStruct.ss32FixVal += rsStruct.ss32FixVal.empty() ? U'x' : rsStruct.ss32FixVal.back() + 1; - rsStruct.wssFixVal += rsStruct.wssFixVal.empty() ? L'x' : rsStruct.wssFixVal.back() + 1; - rsStruct.ptr8Val.resize(rsStruct.ptr8Val.size() * 2); - for (size_t nIndex = 0; nIndex < rsStruct.ptr8Val.size() / 2; nIndex++) - rsStruct.ptr8Val[nIndex + rsStruct.ptr8Val.size() / 2] = rsStruct.ptr8Val[nIndex]; - rsStruct.ptrssVal.resize(rsStruct.ptrssVal.size() * 2); - for (size_t nIndex = 0; nIndex < rsStruct.ptrssVal.size() / 2; nIndex++) - rsStruct.ptrssVal[nIndex + rsStruct.ptrssVal.size() / 2] = rsStruct.ptrssVal[nIndex]; - for (size_t nIndex = 0; nIndex < rsStruct.ptr8FixVal.size(); nIndex++) - rsStruct.ptr8FixVal[nIndex] = static_cast(rsStruct.ptr8FixVal[nIndex] << 1); - for (size_t nIndex = 0; nIndex < rsStruct.ptrssFixVal.size(); nIndex++) - rsStruct.ptrssFixVal[nIndex] = rsStruct.ptrssFixVal[nIndex] + rsStruct.ptrssFixVal[nIndex]; - rsStruct.seq8Val.resize(rsStruct.seq8Val.size() * 2); - for (size_t nIndex = 0; nIndex < rsStruct.seq8Val.size() / 2; nIndex++) - rsStruct.seq8Val[nIndex + rsStruct.seq8Val.size() / 2] = rsStruct.seq8Val[nIndex]; - rsStruct.seqssVal.resize(rsStruct.seqssVal.size() * 2); - for (size_t nIndex = 0; nIndex < rsStruct.seqssVal.size() / 2; nIndex++) - rsStruct.seqssVal[nIndex + rsStruct.seqssVal.size() / 2] = rsStruct.seqssVal[nIndex]; - for (size_t nIndex = 0; nIndex < rsStruct.seq8FixVal.size(); nIndex++) - rsStruct.seq8FixVal[nIndex] = static_cast(rsStruct.seq8FixVal[nIndex] << 1); - for (size_t nIndex = 0; nIndex < rsStruct.seqssFixVal.size(); nIndex++) - rsStruct.seqssFixVal[nIndex] = rsStruct.seqssFixVal[nIndex] + rsStruct.seqssFixVal[nIndex]; - if (rsStruct.ifcVal.get() && rsStruct.ifcVal.id() == sdv::GetInterfaceId()) - { - m_summer.set(rsStruct.ifcVal.get()); - rsStruct.ifcVal = static_cast(&m_summer); - } - else - rsStruct.ifcVal = nullptr; - if (rsStruct.pMultiplyValue) - { - m_multiplier.set(rsStruct.pMultiplyValue); - rsStruct.pMultiplyValue = static_cast(&m_multiplier); - } - else - rsStruct.pMultiplyValue = nullptr; - rsStruct.sSubVal.i += 1; - rsStruct.sIndVal.i += 10; - //rsStruct.sUnnamedVal.i += 20; // 12.04.2024 EVE Unnamed structures are not supported by IDL compiler. - // Unnamed unions (inline definition) are not supported yet: PBI #398209 - //union switch(int32) { case 0: int32 i; default: int32 j; } uUnnamedVal; - // Anonymous structs and unions (unnamed and no declaration) are not supported yet: PBI #397894 - //struct { int32 iAnonymous1; int32 iAnonymous2; }; - //union switch(int32) { case 0: int32 iAnonymous1; default: int32 iAnonymous2; }; - - rsStruct.rgbVal[0] = !rsStruct.rgbVal[0]; - rsStruct.rgbVal[1] = !rsStruct.rgbVal[1]; - rsStruct.rgsVal[0]--; - rsStruct.rgsVal[1]++; - rsStruct.rgusVal[0]++; - rsStruct.rgusVal[1] += 2; - rsStruct.rglVal[0]--; - rsStruct.rglVal[1]++; - rsStruct.rgulVal[0]++; - rsStruct.rgulVal[1] += 2; - rsStruct.rgllVal[0]--; - rsStruct.rgllVal[1]++; - rsStruct.rgullVal[0]++; - rsStruct.rgullVal[1] += 2; - rsStruct.rgi8Val[0]--; - rsStruct.rgi8Val[1]++; - rsStruct.rgui8Val[0]++; - rsStruct.rgui8Val[1] += 2; - rsStruct.rgi16Val[0]--; - rsStruct.rgi16Val[1]++; - rsStruct.rgui16Val[0]++; - rsStruct.rgui16Val[1] += 2; - rsStruct.rgi32Val[0]--; - rsStruct.rgi32Val[1]++; - rsStruct.rgui32Val[0]++; - rsStruct.rgui32Val[1] += 2; - rsStruct.rgi64Val[0]--; - rsStruct.rgi64Val[1]++; - rsStruct.rgui64Val[0]++; - rsStruct.rgui64Val[1] += 2; - rsStruct.rgcVal[0]++; - rsStruct.rgcVal[1] += 2; - rsStruct.rgc16Val[0]++; - rsStruct.rgc16Val[1] += 2; - rsStruct.rgc32Val[0]++; - rsStruct.rgc32Val[1] += 2; - rsStruct.rgwcVal[0]++; - rsStruct.rgwcVal[1] += 2; - rsStruct.rgfVal[0] -= 1000.000f; - rsStruct.rgfVal[1] += 2000.000f; - rsStruct.rgdVal[0] += 1000.000; - rsStruct.rgdVal[1] -= 2000.000; - rsStruct.rgldVal[0] -= 1000.000; - rsStruct.rgldVal[1] += 2000.000; - rsStruct.rgnVal[0]++; - rsStruct.rgnVal[1] += 2; - rsStruct.rgeHelloVal[0] = rsStruct.rgeHelloVal[0]++; - rsStruct.rgeHelloVal[1] = rsStruct.rgeHelloVal[1]--; - if (rsStruct.rgidVal[0] == sdv::GetInterfaceId()) - rsStruct.rgidVal[0] = sdv::GetInterfaceId(); - else - rsStruct.rgidVal[0] = 0; - if (rsStruct.rgidVal[1] == sdv::GetInterfaceId()) - rsStruct.rgidVal[1] = sdv::GetInterfaceId(); - else - rsStruct.rgidVal[1] = 0; - rsStruct.rgssVal[0] += rsStruct.rgssVal[0].empty() ? 'x' : rsStruct.rgssVal[0].back() + 1; - rsStruct.rgssVal[1] += rsStruct.rgssVal[1].empty() ? 'x' : rsStruct.rgssVal[1].front() + 1; - rsStruct.rgss8Val[0] += rsStruct.rgss8Val[0].empty() ? 'x' : rsStruct.rgss8Val[0].back() + 1; - rsStruct.rgss8Val[1] += rsStruct.rgss8Val[1].empty() ? 'x' : rsStruct.rgss8Val[1].front() + 1; - rsStruct.rgss16Val[0] += rsStruct.rgss16Val[0].empty() ? u'x' : rsStruct.rgss16Val[0].back() + 1; - rsStruct.rgss16Val[1] += rsStruct.rgss16Val[1].empty() ? u'x' : rsStruct.rgss16Val[1].front() + 1; - rsStruct.rgss32Val[0] += rsStruct.rgss32Val[0].empty() ? U'x' : rsStruct.rgss32Val[0].back() + 1; - rsStruct.rgss32Val[1] += rsStruct.rgss32Val[1].empty() ? U'x' : rsStruct.rgss32Val[1].front() + 1; - rsStruct.rgwssVal[0] += rsStruct.rgwssVal[0].empty() ? L'x' : rsStruct.rgwssVal[0].back() + 1; - rsStruct.rgwssVal[1] += rsStruct.rgwssVal[1].empty() ? L'x' : rsStruct.rgwssVal[1].front() + 1; - rsStruct.rgssFixVal[0] += rsStruct.rgssFixVal[0].empty() ? 'x' : rsStruct.rgssFixVal[0].back() + 1; - rsStruct.rgssFixVal[1] += rsStruct.rgssFixVal[1].empty() ? 'x' : rsStruct.rgssFixVal[1].front() + 1; - rsStruct.rgss8FixVal[0] += rsStruct.rgss8FixVal[0].empty() ? 'x' : rsStruct.rgss8FixVal[0].back() + 1; - rsStruct.rgss8FixVal[1] += rsStruct.rgss8FixVal[1].empty() ? 'x' : rsStruct.rgss8FixVal[1].front() + 1; - rsStruct.rgss16FixVal[0] += rsStruct.rgss16FixVal[0].empty() ? u'x' : rsStruct.rgss16FixVal[0].back() + 1; - rsStruct.rgss16FixVal[1] += rsStruct.rgss16FixVal[1].empty() ? u'x' : rsStruct.rgss16FixVal[1].front() + 1; - rsStruct.rgss32FixVal[0] += rsStruct.rgss32FixVal[0].empty() ? U'x' : rsStruct.rgss32FixVal[0].back() + 1; - rsStruct.rgss32FixVal[1] += rsStruct.rgss32FixVal[1].empty() ? U'x' : rsStruct.rgss32FixVal[1].front() + 1; - rsStruct.rgwssFixVal[0] += rsStruct.rgwssFixVal[0].empty() ? L'x' : rsStruct.rgwssFixVal[0].back() + 1; - rsStruct.rgwssFixVal[1] += rsStruct.rgwssFixVal[1].empty() ? L'x' : rsStruct.rgwssFixVal[1].front() + 1; - rsStruct.rgptr8Val[0].resize(rsStruct.rgptr8Val[0].size() * 2); - rsStruct.rgptr8Val[1].resize(rsStruct.rgptr8Val[1].size() * 3); - for (size_t nIndex = 0; nIndex < rsStruct.rgptr8Val[0].size() / 2; nIndex++) - rsStruct.rgptr8Val[0][nIndex + rsStruct.rgptr8Val[0].size() / 2] = rsStruct.rgptr8Val[0][nIndex]; - for (size_t nIndex = 0; nIndex < rsStruct.rgptr8Val[1].size() / 3; nIndex++) - rsStruct.rgptr8Val[1][nIndex + rsStruct.rgptr8Val[1].size() / 3] = rsStruct.rgptr8Val[1][nIndex]; - rsStruct.rgptrssVal[0].resize(rsStruct.rgptrssVal[0].size() * 2); - rsStruct.rgptrssVal[1].resize(rsStruct.rgptrssVal[1].size() * 3); - for (size_t nIndex = 0; nIndex < rsStruct.rgptrssVal[0].size() / 2; nIndex++) - rsStruct.rgptrssVal[0][nIndex + rsStruct.rgptrssVal[0].size() / 2] = rsStruct.rgptrssVal[0][nIndex]; - for (size_t nIndex = 0; nIndex < rsStruct.rgptrssVal[1].size() / 3; nIndex++) - rsStruct.rgptrssVal[1][nIndex + rsStruct.rgptrssVal[1].size() / 3] = rsStruct.rgptrssVal[1][nIndex]; - for (size_t nIndex = 0; nIndex < rsStruct.rgptr8FixVal[0].size(); nIndex++) - rsStruct.rgptr8FixVal[0][nIndex] = static_cast(rsStruct.rgptr8FixVal[0][nIndex] << 1); - for (size_t nIndex = 0; nIndex < rsStruct.rgptr8FixVal[1].size(); nIndex++) - rsStruct.rgptr8FixVal[1][nIndex] = static_cast(rsStruct.rgptr8FixVal[1][nIndex] << 1); - for (size_t nIndex = 0; nIndex < rsStruct.rgptrssFixVal[0].size(); nIndex++) - rsStruct.rgptrssFixVal[0][nIndex] = rsStruct.rgptrssFixVal[0][nIndex] + rsStruct.rgptrssFixVal[0][nIndex]; - for (size_t nIndex = 0; nIndex < rsStruct.rgptrssFixVal[1].size(); nIndex++) - rsStruct.rgptrssFixVal[1][nIndex] = rsStruct.rgptrssFixVal[1][nIndex] + rsStruct.rgptrssFixVal[1][nIndex]; - rsStruct.rgseq8Val[0].resize(rsStruct.rgseq8Val[0].size() * 2); - rsStruct.rgseq8Val[1].resize(rsStruct.rgseq8Val[1].size() * 3); - for (size_t nIndex = 0; nIndex < rsStruct.rgseq8Val[0].size() / 2; nIndex++) - rsStruct.rgseq8Val[0][nIndex + rsStruct.rgseq8Val[0].size() / 2] = rsStruct.rgseq8Val[0][nIndex]; - for (size_t nIndex = 0; nIndex < rsStruct.rgseq8Val[1].size() / 3; nIndex++) - rsStruct.rgseq8Val[1][nIndex + rsStruct.rgseq8Val[1].size() / 3] = rsStruct.rgseq8Val[1][nIndex]; - rsStruct.rgseqssVal[0].resize(rsStruct.rgseqssVal[0].size() * 2); - rsStruct.rgseqssVal[1].resize(rsStruct.rgseqssVal[1].size() * 3); - for (size_t nIndex = 0; nIndex < rsStruct.rgseqssVal[0].size() / 2; nIndex++) - rsStruct.rgseqssVal[0][nIndex + rsStruct.rgseqssVal[0].size() / 2] = rsStruct.rgseqssVal[0][nIndex]; - for (size_t nIndex = 0; nIndex < rsStruct.rgseqssVal[1].size() / 3; nIndex++) - rsStruct.rgseqssVal[1][nIndex + rsStruct.rgseqssVal[1].size() / 3] = rsStruct.rgseqssVal[1][nIndex]; - for (size_t nIndex = 0; nIndex < rsStruct.rgseq8FixVal[0].size(); nIndex++) - rsStruct.rgseq8FixVal[0][nIndex] = static_cast(rsStruct.rgseq8FixVal[0][nIndex] << 1); - for (size_t nIndex = 0; nIndex < rsStruct.rgseq8FixVal[1].size(); nIndex++) - rsStruct.rgseq8FixVal[1][nIndex] = static_cast(rsStruct.rgseq8FixVal[1][nIndex] << 1); - for (size_t nIndex = 0; nIndex < rsStruct.rgseqssFixVal[0].size(); nIndex++) - rsStruct.rgseqssFixVal[0][nIndex] = rsStruct.rgseqssFixVal[0][nIndex] + rsStruct.rgseqssFixVal[0][nIndex]; - for (size_t nIndex = 0; nIndex < rsStruct.rgseqssFixVal[1].size(); nIndex++) - rsStruct.rgseqssFixVal[1][nIndex] = rsStruct.rgseqssFixVal[1][nIndex] + rsStruct.rgseqssFixVal[1][nIndex]; - // Incompatible serdes generation: BUG #398509 - //if (rsStruct.rgifcVal[0].get() && rsStruct.rgifcVal[0].id() == sdv::GetInterfaceId()) - //{ - // m_summer.set(rsStruct.rgifcVal[0].get()); - // rsStruct.rgifcVal[0] = static_cast(&m_summer); - //} - //else - // rsStruct.rgifcVal[0] = nullptr; - //if (rsStruct.rgifcVal[1].get() && rsStruct.rgifcVal[1].id() == sdv::GetInterfaceId()) - //{ - // m_summer.set(rsStruct.rgifcVal[1].get()); - // rsStruct.rgifcVal[1] = static_cast(&m_summer); - //} - //else - // rsStruct.rgifcVal[1] = nullptr; - //if (rsStruct.rgpMultiplyValue[0]) - //{ - // m_multiplier.set(rsStruct.rgpMultiplyValue[0]); - // rsStruct.rgpMultiplyValue[0] = static_cast(&m_multiplier); - //} - //else - // rsStruct.rgpMultiplyValue[0] = nullptr; - //if (rsStruct.rgpMultiplyValue[1]) - //{ - // m_multiplier.set(rsStruct.rgpMultiplyValue[1]); - // rsStruct.rgpMultiplyValue[1] = static_cast(&m_multiplier); - //} - //else - // rsStruct.rgpMultiplyValue[1] = nullptr; - rsStruct.rgsSubVal[0].i += 1; - rsStruct.rgsSubVal[1].i += 2; - // Incompatible serdes generation: BUG #398509 - //rsStruct.rgsIndVal[0].i += 10; - //rsStruct.rgsIndVal[1].i += 20; - // Unnamed struct with array is not yet supported due to incorret serdes code generation: BUG #398246 - //rsStruct.rgsUnnamedVal[0].i += 20; - //rsStruct.rgsUnnamedVal[1].i += 40; - - rsStruct.tbVal = !rsStruct.tbVal; - rsStruct.tsVal--; - rsStruct.tusVal++; - rsStruct.tlVal--; - rsStruct.tulVal++; - rsStruct.tllVal--; - rsStruct.tullVal++; - rsStruct.ti8Val--; - rsStruct.tui8Val++; - rsStruct.ti16Val--; - rsStruct.tui16Val++; - rsStruct.ti32Val--; - rsStruct.tui32Val++; - rsStruct.ti64Val--; - rsStruct.tui64Val++; - rsStruct.tcVal++; - rsStruct.tc16Val++; - rsStruct.tc32Val++; - rsStruct.twcVal++; - rsStruct.tfVal -= 1000.000f; - rsStruct.tdVal += 1000.000; - rsStruct.tldVal -= 1000.000; - rsStruct.tnVal++; - rsStruct.teHelloVal = rsStruct.teHelloVal++; - if (rsStruct.tidVal == sdv::GetInterfaceId()) - rsStruct.tidVal = sdv::GetInterfaceId(); - else - rsStruct.tidVal = 0; - rsStruct.tssVal += rsStruct.tssVal.empty() ? 'x' : rsStruct.tssVal.back() + 1; - rsStruct.tss8Val += rsStruct.tss8Val.empty() ? 'x' : rsStruct.tss8Val.back() + 1; - rsStruct.tss16Val += rsStruct.tss16Val.empty() ? u'x' : rsStruct.tss16Val.back() + 1; - rsStruct.tss32Val += rsStruct.tss32Val.empty() ? U'x' : rsStruct.tss32Val.back() + 1; - rsStruct.twssVal += rsStruct.twssVal.empty() ? L'x' : rsStruct.twssVal.back() + 1; - rsStruct.tssFixVal += rsStruct.tssFixVal.empty() ? 'x' : rsStruct.tssFixVal.back() + 1; - rsStruct.tss8FixVal += rsStruct.tss8FixVal.empty() ? 'x' : rsStruct.tss8FixVal.back() + 1; - rsStruct.tss16FixVal += rsStruct.tss16FixVal.empty() ? u'x' : rsStruct.tss16FixVal.back() + 1; - rsStruct.tss32FixVal += rsStruct.tss32FixVal.empty() ? U'x' : rsStruct.tss32FixVal.back() + 1; - rsStruct.twssFixVal += rsStruct.twssFixVal.empty() ? L'x' : rsStruct.twssFixVal.back() + 1; - // Using typedefs of template based types produced an error: BUG #398266 - //rsStruct.tptr8Val.resize(rsStruct.tptr8Val.size() * 2); - //for (size_t nIndex = 0; nIndex < rsStruct.tptr8Val.size() / 2; nIndex++) - // rsStruct.tptr8Val[nIndex + rsStruct.tptr8Val.size() / 2] = rsStruct.tptr8Val[nIndex]; - //rsStruct.ptrssVal.resize(rsStruct.tptrssVal.size() * 2); - //for (size_t nIndex = 0; nIndex < rsStruct.tptrssVal.size() / 2; nIndex++) - // rsStruct.tptrssVal[nIndex + rsStruct.tptrssVal.size() / 2] = rsStruct.tptrssVal[nIndex]; - //for (size_t nIndex = 0; nIndex < rsStruct.tptr8FixVal.size(); nIndex++) - // rsStruct.tptr8FixVal[nIndex] = static_cast(rsStruct.tptr8FixVal[nIndex] << 1); - //for (size_t nIndex = 0; nIndex < rsStruct.tptrssFixVal.size(); nIndex++) - // rsStruct.ptrssFixVal[nIndex] = rsStruct.tptrssFixVal[nIndex] + rsStruct.tptrssFixVal[nIndex]; - //rsStruct.tseq8Val.resize(rsStruct.tseq8Val.size() * 2); - //for (size_t nIndex = 0; nIndex < rsStruct.tseq8Val.size() / 2; nIndex++) - // rsStruct.tseq8Val[nIndex + rsStruct.tseq8Val.size() / 2] = rsStruct.tseq8Val[nIndex]; - //rsStruct.tseqssVal.resize(rsStruct.tseqssVal.size() * 2); - //for (size_t nIndex = 0; nIndex < rsStruct.tseqssVal.size() / 2; nIndex++) - // rsStruct.seqssVal[nIndex + rsStruct.tseqssVal.size() / 2] = rsStruct.tseqssVal[nIndex]; - //for (size_t nIndex = 0; nIndex < rsStruct.tseq8FixVal.size(); nIndex++) - // rsStruct.tseq8FixVal[nIndex] = static_cast(rsStruct.tseq8FixVal[nIndex] << 1); - //for (size_t nIndex = 0; nIndex < rsStruct.tseqssFixVal.size(); nIndex++) - // rsStruct.tseqssFixVal[nIndex] = rsStruct.tseqssFixVal[nIndex] + rsStruct.tseqssFixVal[nIndex]; - if (rsStruct.tifcVal.get() && rsStruct.tifcVal.id() == sdv::GetInterfaceId()) - { - m_summerTypedef.set(rsStruct.tifcVal.get()); - rsStruct.tifcVal = static_cast(&m_summerTypedef); - } - else - rsStruct.tifcVal = nullptr; - // Typedef of interfaces current not possible. BUG #399464 - //if (rsStruct.tpMultiplyValue) - //{ - // m_multiplierTypedef.set(rsStruct.tpMultiplyValue); - // rsStruct.tpMultiplyValue = static_cast(&m_multiplierTypedef); - //} - //else - // rsStruct.tpMultiplyValue = nullptr; - rsStruct.tsSubVal.i += 1; - rsStruct.tsIndVal.i += 10; - // Incompatible serdes generation: BUG #398509 - //rsStruct.tsUnnamedVal.i += 20; - // Unnamed unions (inline definition) are not supported yet: PBI #398209 - //union switch(int32) { case 0: int32 i; default: int32 j; } tuUnnamedVal; - // Anonymous structs and unions (unnamed and no declaration) are not supported yet: PBI #397894 - //struct { int32 iAnonymous1; int32 iAnonymous2; }; - //union switch(int32) { case 0: int32 iAnonymous1; default: int32 iAnonymous2; }; - } - - /** - * @brief Update the mega union. This will test marshalling and unmarshalling of the mega union. Overload of - * IMegaTest::ProcessMegaTypeBasedUnion. - */ - virtual void ProcessMegaTypeBasedUnion(/*inout*/ UMegaTypeBasedUnion& ruUnion) override - { - switch (ruUnion.get_switch()) - { - case 1: ruUnion.bVal = !ruUnion.bVal; break; - case 2: ruUnion.sVal--; break; - case 3: ruUnion.usVal++; break; - case 4: ruUnion.lVal--; break; - case 5: ruUnion.ulVal++; break; - case 6: ruUnion.llVal--; break; - case 7: ruUnion.ullVal++; break; - case 8: ruUnion.i8Val--; break; - case 9: ruUnion.ui8Val++; break; - case 10: ruUnion.i16Val--; break; - case 11: ruUnion.ui16Val++; break; - case 12: ruUnion.i32Val--; break; - case 13: ruUnion.ui32Val++; break; - case 14: ruUnion.i64Val--; break; - case 15: ruUnion.ui64Val++; break; - case 16: ruUnion.cVal++; break; - case 17: ruUnion.c16Val++; break; - case 18: ruUnion.c32Val++; break; - case 19: ruUnion.wcVal++; break; - case 20: ruUnion.fVal -= 1000.000f; break; - case 21: ruUnion.dVal += 1000.000; break; - // GCC issue with generated initialization in the consructor for the "long double" type: BUG #3982727 - //case 22: ruUnion.ldVal -= 1000.000; break; - case 23: ruUnion.nVal++; break; - case 24: ruUnion.eHelloVal = ruUnion.eHelloVal++; break; - case 25: - if (ruUnion.idVal == sdv::GetInterfaceId()) - ruUnion.idVal = sdv::GetInterfaceId(); - else - ruUnion.idVal = 0; - break; - case 26: ruUnion.ssVal += ruUnion.ssVal.empty() ? 'x' : ruUnion.ssVal.back() + 1; break; - case 27: ruUnion.ss8Val += ruUnion.ss8Val.empty() ? 'x' : ruUnion.ss8Val.back() + 1; break; - case 28: ruUnion.ss16Val += ruUnion.ss16Val.empty() ? u'x' : ruUnion.ss16Val.back() + 1; break; - case 29: ruUnion.ss32Val += ruUnion.ss32Val.empty() ? U'x' : ruUnion.ss32Val.back() + 1; break; - case 30: ruUnion.wssVal += ruUnion.wssVal.empty() ? L'x' : ruUnion.wssVal.back() + 1; break; - case 31: ruUnion.ssFixVal += ruUnion.ssFixVal.empty() ? 'x' : ruUnion.ssFixVal.back() + 1; break; - case 32: ruUnion.ss8FixVal += ruUnion.ss8FixVal.empty() ? 'x' : ruUnion.ss8FixVal.back() + 1; break; - case 33: ruUnion.ss16FixVal += ruUnion.ss16FixVal.empty() ? u'x' : ruUnion.ss16FixVal.back() + 1; break; - case 34: ruUnion.ss32FixVal += ruUnion.ss32FixVal.empty() ? U'x' : ruUnion.ss32FixVal.back() + 1; break; - case 35: ruUnion.wssFixVal += ruUnion.wssFixVal.empty() ? L'x' : ruUnion.wssFixVal.back() + 1; break; - case 36: - ruUnion.ptr8Val.resize(ruUnion.ptr8Val.size() * 2); - for (size_t nIndex = 0; nIndex < ruUnion.ptr8Val.size() / 2; nIndex++) - ruUnion.ptr8Val[nIndex + ruUnion.ptr8Val.size() / 2] = ruUnion.ptr8Val[nIndex]; - break; - case 37: - ruUnion.ptrssVal.resize(ruUnion.ptrssVal.size() * 2); - for (size_t nIndex = 0; nIndex < ruUnion.ptrssVal.size() / 2; nIndex++) - ruUnion.ptrssVal[nIndex + ruUnion.ptrssVal.size() / 2] = ruUnion.ptrssVal[nIndex]; - break; - case 38: - for (size_t nIndex = 0; nIndex < ruUnion.ptr8FixVal.size(); nIndex++) - ruUnion.ptr8FixVal[nIndex] = static_cast(ruUnion.ptr8FixVal[nIndex] << 1); - break; - case 39: - for (size_t nIndex = 0; nIndex < ruUnion.ptrssFixVal.size(); nIndex++) - ruUnion.ptrssFixVal[nIndex] = ruUnion.ptrssFixVal[nIndex] + ruUnion.ptrssFixVal[nIndex]; - break; - case 40: - ruUnion.seq8Val.resize(ruUnion.seq8Val.size() * 2); - for (size_t nIndex = 0; nIndex < ruUnion.seq8Val.size() / 2; nIndex++) - ruUnion.seq8Val[nIndex + ruUnion.seq8Val.size() / 2] = ruUnion.seq8Val[nIndex]; - break; - case 41: - ruUnion.seqssVal.resize(ruUnion.seqssVal.size() * 2); - for (size_t nIndex = 0; nIndex < ruUnion.seqssVal.size() / 2; nIndex++) - ruUnion.seqssVal[nIndex + ruUnion.seqssVal.size() / 2] = ruUnion.seqssVal[nIndex]; - break; - case 42: - for (size_t nIndex = 0; nIndex < ruUnion.seq8FixVal.size(); nIndex++) - ruUnion.seq8FixVal[nIndex] = static_cast(ruUnion.seq8FixVal[nIndex] << 1); - break; - case 43: - for (size_t nIndex = 0; nIndex < ruUnion.seqssFixVal.size(); nIndex++) - ruUnion.seqssFixVal[nIndex] = ruUnion.seqssFixVal[nIndex] + ruUnion.seqssFixVal[nIndex]; - break; - case 44: - if (ruUnion.ifcVal.get() && ruUnion.ifcVal.id() == sdv::GetInterfaceId()) - { - m_summer.set(ruUnion.ifcVal.get()); - ruUnion.ifcVal = static_cast(&m_summer); - } - else - ruUnion.ifcVal = nullptr; - break; - case 45: - if (ruUnion.pMultiplyValue) - { - m_multiplier.set(ruUnion.pMultiplyValue); - ruUnion.pMultiplyValue = static_cast(&m_multiplier); - } - else - ruUnion.pMultiplyValue = nullptr; - break; - case 46: ruUnion.sSubVal.i += 1; break; - // Incompatible serdes generation: BUG #398509 - //case 47: ruUnion.sIndVal.i += 10; break; - // Unnamed struct inside unions are not supported yet: PBI #398230 - //case 48: ruUnion.sUnnamedVal.i += 20; break; - // Unnamed unions are not supported yet: PBI #398209 - //case 49: union switch(int32) { case 0: int32 i; default: int32 j; } uUnnamedVal; - // Anonymous structs and unions (unnamed and no declaration) are not supported yet: PBI #397894 - //case 50: struct { int32 iAnonymous1; int32 iAnonymous2; }; - //case 51: union switch(int32) { case 0: int32 iAnonymous1; default: int32 iAnonymous2; }; - - case 201: ruUnion.tbVal = !ruUnion.tbVal; break; - case 202: ruUnion.tsVal--; break; - case 203: ruUnion.tusVal++; break; - case 204: ruUnion.tlVal--; break; - case 205: ruUnion.tulVal++; break; - case 206: ruUnion.tllVal--; break; - case 207: ruUnion.tullVal++; break; - case 208: ruUnion.ti8Val--; break; - case 209: ruUnion.tui8Val++; break; - case 210: ruUnion.ti16Val--; break; - case 211: ruUnion.tui16Val++; break; - case 212: ruUnion.ti32Val--; break; - case 213: ruUnion.tui32Val++; break; - case 214: ruUnion.ti64Val--; break; - case 215: ruUnion.tui64Val++; break; - case 216: ruUnion.tcVal++; break; - case 217: ruUnion.tc16Val++; break; - case 218: ruUnion.tc32Val++; break; - case 219: ruUnion.twcVal++; break; - case 220: ruUnion.tfVal -= 1000.000f; break; - case 221: ruUnion.tdVal += 1000.000; break; - // GCC issue with generated initialization in the consructor for the "long double" type: BUG #3982727 - //case 22: ruUnion.tldVal -= 1000.000; break; - case 223: ruUnion.tnVal++; break; - case 224: ruUnion.teHelloVal = ruUnion.teHelloVal++; break; - case 225: - if (ruUnion.tidVal == sdv::GetInterfaceId()) - ruUnion.tidVal = sdv::GetInterfaceId(); - else - ruUnion.tidVal = 0; - break; - case 226: ruUnion.tssVal += ruUnion.tssVal.empty() ? 'x' : ruUnion.tssVal.back() + 1; break; - case 227: ruUnion.tss8Val += ruUnion.tss8Val.empty() ? 'x' : ruUnion.tss8Val.back() + 1; break; - case 228: ruUnion.tss16Val += ruUnion.tss16Val.empty() ? u'x' : ruUnion.tss16Val.back() + 1; break; - case 229: ruUnion.tss32Val += ruUnion.tss32Val.empty() ? U'x' : ruUnion.tss32Val.back() + 1; break; - case 230: ruUnion.twssVal += ruUnion.twssVal.empty() ? L'x' : ruUnion.twssVal.back() + 1; break; - case 231: ruUnion.tssFixVal += ruUnion.tssFixVal.empty() ? 'x' : ruUnion.tssFixVal.back() + 1; break; - case 232: ruUnion.tss8FixVal += ruUnion.tss8FixVal.empty() ? 'x' : ruUnion.tss8FixVal.back() + 1; break; - case 233: ruUnion.tss16FixVal += ruUnion.tss16FixVal.empty() ? u'x' : ruUnion.tss16FixVal.back() + 1; break; - case 234: ruUnion.tss32FixVal += ruUnion.tss32FixVal.empty() ? U'x' : ruUnion.tss32FixVal.back() + 1; break; - case 235: ruUnion.twssFixVal += ruUnion.twssFixVal.empty() ? L'x' : ruUnion.twssFixVal.back() + 1; break; - // Using typedefs of template based types produced an error: BUG #398266 - //case 236: - // ruUnion.tptr8Val.resize(ruUnion.tptr8Val.size() * 2); - // for (size_t nIndex = 0; nIndex < ruUnion.tptr8Val.size() / 2; nIndex++) - // ruUnion.tptr8Val[nIndex + ruUnion.tptr8Val.size() / 2] = ruUnion.tptr8Val[nIndex]; - // break; - //case 237: - // ruUnion.tptrssVal.resize(ruUnion.tptrssVal.size() * 2); - // for (size_t nIndex = 0; nIndex < ruUnion.tptrssVal.size() / 2; nIndex++) - // ruUnion.tptrssVal[nIndex + ruUnion.tptrssVal.size() / 2] = ruUnion.tptrssVal[nIndex]; - // break; - //case 238: - // for (size_t nIndex = 0; nIndex < ruUnion.tptr8FixVal.size(); nIndex++) - // ruUnion.tptr8FixVal[nIndex] = static_cast(ruUnion.tptr8FixVal[nIndex] << 1); - // break; - //case 239: - // for (size_t nIndex = 0; nIndex < ruUnion.tptrssFixVal.size(); nIndex++) - // ruUnion.tptrssFixVal[nIndex] = ruUnion.tptrssFixVal[nIndex] + ruUnion.tptrssFixVal[nIndex]; - // break; - //case 240: - // ruUnion.tseq8Val.resize(ruUnion.tseq8Val.size() * 2); - // for (size_t nIndex = 0; nIndex < ruUnion.tseq8Val.size() / 2; nIndex++) - // ruUnion.tseq8Val[nIndex + ruUnion.tseq8Val.size() / 2] = ruUnion.tseq8Val[nIndex]; - // break; - //case 241: - // ruUnion.tseqssVal.resize(ruUnion.tseqssVal.size() * 2); - // for (size_t nIndex = 0; nIndex < ruUnion.tseqssVal.size() / 2; nIndex++) - // ruUnion.tseqssVal[nIndex + ruUnion.tseqssVal.size() / 2] = ruUnion.tseqssVal[nIndex]; - // break; - //case 242: - // for (size_t nIndex = 0; nIndex < ruUnion.tseq8FixVal.size(); nIndex++) - // ruUnion.tseq8FixVal[nIndex] = static_cast(ruUnion.tseq8FixVal[nIndex] << 1); - // break; - //case 243: - // for (size_t nIndex = 0; nIndex < ruUnion.tseqssFixVal.size(); nIndex++) - // ruUnion.tseqssFixVal[nIndex] = ruUnion.tseqssFixVal[nIndex] + ruUnion.tseqssFixVal[nIndex]; - // break; - case 244: - if (ruUnion.tifcVal.get() && ruUnion.tifcVal.id() == sdv::GetInterfaceId()) - { - m_summerTypedef.set(ruUnion.tifcVal.get()); - ruUnion.tifcVal = static_cast(&m_summerTypedef); - } - else - ruUnion.tifcVal = nullptr; - break; - // Typedef of interfaces current not possible. BUG #399464 - //case 245: - // if (ruUnion.tpMultiplyValue) - // { - // m_multiplierTypedef.set(ruUnion.tpMultiplyValue); - // ruUnion.tpMultiplyValue = static_cast(&m_multiplierTypedef); - // } - // else - // ruUnion.tpMultiplyValue = nullptr; - // break; - case 246: ruUnion.tsSubVal.i += 1; break; - // Incompatible serdes generation: BUG #398509 - case 247: ruUnion.tsIndVal.i += 10; break; - // Unnamed struct inside unions are not supported yet: PBI #398230 - //case 248: ruUnion.tsUnnamedVal.i += 20; break; - // Unnamed unions are not supported yet: PBI #398209 - //case 249: union switch(int32) { case 0: int32 i; default: int32 j; } uUnnamedVal; - // Anonymous structs and unions (unnamed and no declaration) are not supported yet: PBI #397894 - //case 250: struct { int32 iAnonymous1; int32 iAnonymous2; }; - //case 251: union switch(int32) { case 0: int32 iAnonymous1; default: int32 iAnonymous2; }; - default: break; - } - } - - /** - * @brief Update the mega union. This will test marshalling and unmarshalling of the mega union. Overload of - * IMegaTest::ProcessMegaTypeBasedUnion. - */ - virtual void ProcessMegaVarBasedUnion(/*inout*/ SMegaVarBasedUnion& rsUnion) override - { - switch (rsUnion.get_switch()) - { - case 1: rsUnion.uVal.bVal = !rsUnion.uVal.bVal; break; - case 2: rsUnion.uVal.sVal--; break; - case 3: rsUnion.uVal.usVal++; break; - case 4: rsUnion.uVal.lVal--; break; - case 5: rsUnion.uVal.ulVal++; break; - case 6: rsUnion.uVal.llVal--; break; - case 7: rsUnion.uVal.ullVal++; break; - case 8: rsUnion.uVal.i8Val--; break; - case 9: rsUnion.uVal.ui8Val++; break; - case 10: rsUnion.uVal.i16Val--; break; - case 11: rsUnion.uVal.ui16Val++; break; - case 12: rsUnion.uVal.i32Val--; break; - case 13: rsUnion.uVal.ui32Val++; break; - case 14: rsUnion.uVal.i64Val--; break; - case 15: rsUnion.uVal.ui64Val++; break; - case 16: rsUnion.uVal.cVal++; break; - case 17: rsUnion.uVal.c16Val++; break; - case 18: rsUnion.uVal.c32Val++; break; - case 19: rsUnion.uVal.wcVal++; break; - case 20: rsUnion.uVal.fVal -= 1000.000f; break; - case 21: rsUnion.uVal.dVal += 1000.000; break; - // GCC issue with generated initialization in the consructor for the "long double" type: BUG #3982727 - //case 22: rsUnion.uVal.ldVal -= 1000.000; break; - case 23: rsUnion.uVal.nVal++; break; - case 24: rsUnion.uVal.eHelloVal = rsUnion.uVal.eHelloVal++; break; - case 25: - if (rsUnion.uVal.idVal == sdv::GetInterfaceId()) - rsUnion.uVal.idVal = sdv::GetInterfaceId(); - else - rsUnion.uVal.idVal = 0; - break; - case 26: rsUnion.uVal.ssVal += rsUnion.uVal.ssVal.empty() ? 'x' : rsUnion.uVal.ssVal.back() + 1; break; - case 27: rsUnion.uVal.ss8Val += rsUnion.uVal.ss8Val.empty() ? 'x' : rsUnion.uVal.ss8Val.back() + 1; break; - case 28: rsUnion.uVal.ss16Val += rsUnion.uVal.ss16Val.empty() ? u'x' : rsUnion.uVal.ss16Val.back() + 1; break; - case 29: rsUnion.uVal.ss32Val += rsUnion.uVal.ss32Val.empty() ? U'x' : rsUnion.uVal.ss32Val.back() + 1; break; - case 30: rsUnion.uVal.wssVal += rsUnion.uVal.wssVal.empty() ? L'x' : rsUnion.uVal.wssVal.back() + 1; break; - case 31: rsUnion.uVal.ssFixVal += rsUnion.uVal.ssFixVal.empty() ? 'x' : rsUnion.uVal.ssFixVal.back() + 1; break; - case 32: rsUnion.uVal.ss8FixVal += rsUnion.uVal.ss8FixVal.empty() ? 'x' : rsUnion.uVal.ss8FixVal.back() + 1; break; - case 33: rsUnion.uVal.ss16FixVal += rsUnion.uVal.ss16FixVal.empty() ? u'x' : rsUnion.uVal.ss16FixVal.back() + 1; break; - case 34: rsUnion.uVal.ss32FixVal += rsUnion.uVal.ss32FixVal.empty() ? U'x' : rsUnion.uVal.ss32FixVal.back() + 1; break; - case 35: rsUnion.uVal.wssFixVal += rsUnion.uVal.wssFixVal.empty() ? L'x' : rsUnion.uVal.wssFixVal.back() + 1; break; - case 36: - rsUnion.uVal.ptr8Val.resize(rsUnion.uVal.ptr8Val.size() * 2); - for (size_t nIndex = 0; nIndex < rsUnion.uVal.ptr8Val.size() / 2; nIndex++) - rsUnion.uVal.ptr8Val[nIndex + rsUnion.uVal.ptr8Val.size() / 2] = rsUnion.uVal.ptr8Val[nIndex]; - break; - case 37: - rsUnion.uVal.ptrssVal.resize(rsUnion.uVal.ptrssVal.size() * 2); - for (size_t nIndex = 0; nIndex < rsUnion.uVal.ptrssVal.size() / 2; nIndex++) - rsUnion.uVal.ptrssVal[nIndex + rsUnion.uVal.ptrssVal.size() / 2] = rsUnion.uVal.ptrssVal[nIndex]; - break; - case 38: - for (size_t nIndex = 0; nIndex < rsUnion.uVal.ptr8FixVal.size(); nIndex++) - rsUnion.uVal.ptr8FixVal[nIndex] = static_cast(rsUnion.uVal.ptr8FixVal[nIndex] << 1); - break; - case 39: - for (size_t nIndex = 0; nIndex < rsUnion.uVal.ptrssFixVal.size(); nIndex++) - rsUnion.uVal.ptrssFixVal[nIndex] = rsUnion.uVal.ptrssFixVal[nIndex] + rsUnion.uVal.ptrssFixVal[nIndex]; - break; - case 40: - rsUnion.uVal.seq8Val.resize(rsUnion.uVal.seq8Val.size() * 2); - for (size_t nIndex = 0; nIndex < rsUnion.uVal.seq8Val.size() / 2; nIndex++) - rsUnion.uVal.seq8Val[nIndex + rsUnion.uVal.seq8Val.size() / 2] = rsUnion.uVal.seq8Val[nIndex]; - break; - case 41: - rsUnion.uVal.seqssVal.resize(rsUnion.uVal.seqssVal.size() * 2); - for (size_t nIndex = 0; nIndex < rsUnion.uVal.seqssVal.size() / 2; nIndex++) - rsUnion.uVal.seqssVal[nIndex + rsUnion.uVal.seqssVal.size() / 2] = rsUnion.uVal.seqssVal[nIndex]; - break; - case 42: - for (size_t nIndex = 0; nIndex < rsUnion.uVal.seq8FixVal.size(); nIndex++) - rsUnion.uVal.seq8FixVal[nIndex] = static_cast(rsUnion.uVal.seq8FixVal[nIndex] << 1); - break; - case 43: - for (size_t nIndex = 0; nIndex < rsUnion.uVal.seqssFixVal.size(); nIndex++) - rsUnion.uVal.seqssFixVal[nIndex] = rsUnion.uVal.seqssFixVal[nIndex] + rsUnion.uVal.seqssFixVal[nIndex]; - break; - case 44: - if (rsUnion.uVal.ifcVal.get() && rsUnion.uVal.ifcVal.id() == sdv::GetInterfaceId()) - { - m_summer.set(rsUnion.uVal.ifcVal.get()); - rsUnion.uVal.ifcVal = static_cast(&m_summer); - } - else - rsUnion.uVal.ifcVal = nullptr; - break; - case 45: - if (rsUnion.uVal.pMultiplyValue) - { - m_multiplier.set(rsUnion.uVal.pMultiplyValue); - rsUnion.uVal.pMultiplyValue = static_cast(&m_multiplier); - } - else - rsUnion.uVal.pMultiplyValue = nullptr; - break; - case 46: rsUnion.uVal.sSubVal.i += 1; break; - // Incompatible serdes generation: BUG #398509 - //case 47: rsUnion.uVal.sIndVal.i += 10; break; - // Unnamed struct inside unions are not supported yet: PBI #398230 - //case 48: rsUnion.uVal.sUnnamedVal.i += 20; break; - // Unnamed unions are not supported yet: PBI #398209 - //case 49: union switch(int32) { case 0: int32 i; default: int32 j; } uUnnamedVal; - // Anonymous structs and unions (unnamed and no declaration) are not supported yet: PBI #397894 - //case 50: struct { int32 iAnonymous1; int32 iAnonymous2; }; - //case 51: union switch(int32) { case 0: int32 iAnonymous1; default: int32 iAnonymous2; }; - - case 201: rsUnion.uVal.tbVal = !rsUnion.uVal.tbVal; break; - case 202: rsUnion.uVal.tsVal--; break; - case 203: rsUnion.uVal.tusVal++; break; - case 204: rsUnion.uVal.tlVal--; break; - case 205: rsUnion.uVal.tulVal++; break; - case 206: rsUnion.uVal.tllVal--; break; - case 207: rsUnion.uVal.tullVal++; break; - case 208: rsUnion.uVal.ti8Val--; break; - case 209: rsUnion.uVal.tui8Val++; break; - case 210: rsUnion.uVal.ti16Val--; break; - case 211: rsUnion.uVal.tui16Val++; break; - case 212: rsUnion.uVal.ti32Val--; break; - case 213: rsUnion.uVal.tui32Val++; break; - case 214: rsUnion.uVal.ti64Val--; break; - case 215: rsUnion.uVal.tui64Val++; break; - case 216: rsUnion.uVal.tcVal++; break; - case 217: rsUnion.uVal.tc16Val++; break; - case 218: rsUnion.uVal.tc32Val++; break; - case 219: rsUnion.uVal.twcVal++; break; - case 220: rsUnion.uVal.tfVal -= 1000.000f; break; - case 221: rsUnion.uVal.tdVal += 1000.000; break; - // GCC issue with generated initialization in the consructor for the "long double" type: BUG #3982727 - //case 22: rsUnion.uVal.tldVal -= 1000.000; break; - case 223: rsUnion.uVal.tnVal++; break; - case 224: rsUnion.uVal.teHelloVal = rsUnion.uVal.teHelloVal++; break; - case 225: - if (rsUnion.uVal.tidVal == sdv::GetInterfaceId()) - rsUnion.uVal.tidVal = sdv::GetInterfaceId(); - else - rsUnion.uVal.tidVal = 0; - break; - case 226: rsUnion.uVal.tssVal += rsUnion.uVal.tssVal.empty() ? 'x' : rsUnion.uVal.tssVal.back() + 1; break; - case 227: rsUnion.uVal.tss8Val += rsUnion.uVal.tss8Val.empty() ? 'x' : rsUnion.uVal.tss8Val.back() + 1; break; - case 228: rsUnion.uVal.tss16Val += rsUnion.uVal.tss16Val.empty() ? u'x' : rsUnion.uVal.tss16Val.back() + 1; break; - case 229: rsUnion.uVal.tss32Val += rsUnion.uVal.tss32Val.empty() ? U'x' : rsUnion.uVal.tss32Val.back() + 1; break; - case 230: rsUnion.uVal.twssVal += rsUnion.uVal.twssVal.empty() ? L'x' : rsUnion.uVal.twssVal.back() + 1; break; - case 231: rsUnion.uVal.tssFixVal += rsUnion.uVal.tssFixVal.empty() ? 'x' : rsUnion.uVal.tssFixVal.back() + 1; break; - case 232: rsUnion.uVal.tss8FixVal += rsUnion.uVal.tss8FixVal.empty() ? 'x' : rsUnion.uVal.tss8FixVal.back() + 1; break; - case 233: rsUnion.uVal.tss16FixVal += rsUnion.uVal.tss16FixVal.empty() ? u'x' : rsUnion.uVal.tss16FixVal.back() + 1; break; - case 234: rsUnion.uVal.tss32FixVal += rsUnion.uVal.tss32FixVal.empty() ? U'x' : rsUnion.uVal.tss32FixVal.back() + 1; break; - case 235: rsUnion.uVal.twssFixVal += rsUnion.uVal.twssFixVal.empty() ? L'x' : rsUnion.uVal.twssFixVal.back() + 1; break; - // Using typedefs of template based types produced an error: BUG #398266 - //case 236: - // rsUnion.uVal.tptr8Val.resize(rsUnion.uVal.tptr8Val.size() * 2); - // for (size_t nIndex = 0; nIndex < rsUnion.uVal.tptr8Val.size() / 2; nIndex++) - // rsUnion.uVal.tptr8Val[nIndex + rsUnion.uVal.tptr8Val.size() / 2] = rsUnion.uVal.tptr8Val[nIndex]; - // break; - //case 237: - // rsUnion.uVal.tptrssVal.resize(rsUnion.uVal.tptrssVal.size() * 2); - // for (size_t nIndex = 0; nIndex < rsUnion.uVal.tptrssVal.size() / 2; nIndex++) - // rsUnion.uVal.tptrssVal[nIndex + rsUnion.uVal.tptrssVal.size() / 2] = rsUnion.uVal.tptrssVal[nIndex]; - // break; - //case 238: - // for (size_t nIndex = 0; nIndex < rsUnion.uVal.tptr8FixVal.size(); nIndex++) - // rsUnion.uVal.tptr8FixVal[nIndex] = static_cast(rsUnion.uVal.tptr8FixVal[nIndex] << 1); - // break; - //case 239: - // for (size_t nIndex = 0; nIndex < rsUnion.uVal.tptrssFixVal.size(); nIndex++) - // rsUnion.uVal.tptrssFixVal[nIndex] = rsUnion.uVal.tptrssFixVal[nIndex] + rsUnion.uVal.tptrssFixVal[nIndex]; - // break; - //case 240: - // rsUnion.uVal.tseq8Val.resize(rsUnion.uVal.tseq8Val.size() * 2); - // for (size_t nIndex = 0; nIndex < rsUnion.uVal.tseq8Val.size() / 2; nIndex++) - // rsUnion.uVal.tseq8Val[nIndex + rsUnion.uVal.tseq8Val.size() / 2] = rsUnion.uVal.tseq8Val[nIndex]; - // break; - //case 241: - // rsUnion.uVal.tseqssVal.resize(rsUnion.uVal.tseqssVal.size() * 2); - // for (size_t nIndex = 0; nIndex < rsUnion.uVal.tseqssVal.size() / 2; nIndex++) - // rsUnion.uVal.tseqssVal[nIndex + rsUnion.uVal.tseqssVal.size() / 2] = rsUnion.uVal.tseqssVal[nIndex]; - // break; - //case 242: - // for (size_t nIndex = 0; nIndex < rsUnion.uVal.tseq8FixVal.size(); nIndex++) - // rsUnion.uVal.tseq8FixVal[nIndex] = static_cast(rsUnion.uVal.tseq8FixVal[nIndex] << 1); - // break; - //case 243: - // for (size_t nIndex = 0; nIndex < rsUnion.uVal.tseqssFixVal.size(); nIndex++) - // rsUnion.uVal.tseqssFixVal[nIndex] = rsUnion.uVal.tseqssFixVal[nIndex] + rsUnion.uVal.tseqssFixVal[nIndex]; - // break; - case 244: - if (rsUnion.uVal.tifcVal.get() && rsUnion.uVal.tifcVal.id() == sdv::GetInterfaceId()) - { - m_summerTypedef.set(rsUnion.uVal.tifcVal.get()); - rsUnion.uVal.tifcVal = static_cast(&m_summerTypedef); - } - else - rsUnion.uVal.tifcVal = nullptr; - break; - // Typedef of interfaces current not possible. BUG #399464 - //case 245: - // if (rsUnion.uVal.tpMultiplyValue) - // { - // m_multiplierTypedef.set(rsUnion.uVal.tpMultiplyValue); - // rsUnion.uVal.tpMultiplyValue = static_cast(&m_multiplierTypedef); - // } - // else - // rsUnion.uVal.tpMultiplyValue = nullptr; - // break; - case 246: rsUnion.uVal.tsSubVal.i += 1; break; - // Incompatible serdes generation: BUG #398509 - case 247: rsUnion.uVal.tsIndVal.i += 10; break; - // Unnamed struct inside unions are not supported yet: PBI #398230 - //case 248: rsUnion.uVal.tsUnnamedVal.i += 20; break; - // Unnamed unions are not supported yet: PBI #398209 - //case 249: union switch(int32) { case 0: int32 i; default: int32 j; } uUnnamedVal; - // Anonymous structs and unions (unnamed and no declaration) are not supported yet: PBI #397894 - //case 250: struct { int32 iAnonymous1; int32 iAnonymous2; }; - //case 251: union switch(int32) { case 0: int32 iAnonymous1; default: int32 iAnonymous2; }; - default: break; - } - } - - ISayHello* m_pHello = nullptr; - - struct SInternalMultiply : IMultiplyValue - { - void set(IMultiplyValue* p) { pMultiplyValue = p; } - size_t Multiply(size_t n) - { - if (!pMultiplyValue) return 0; - return pMultiplyValue->Multiply(n) * pMultiplyValue->Multiply(n); - } - IMultiplyValue* pMultiplyValue = nullptr; - }; - SInternalMultiply m_multiplier; - SInternalMultiply m_multiplierTypedef; - - struct SInternalAdd : IAddValue - { - void set(IMultiplyValue* p) { pMultiplyValue = p; } - size_t Add(size_t n) - { - if (!pMultiplyValue) return 0; - return pMultiplyValue->Multiply(n) + pMultiplyValue->Multiply(n); - } - IMultiplyValue* pMultiplyValue = nullptr; - }; - SInternalAdd m_summer; - SInternalAdd m_summerTypedef; -}; - -void LoadIPCModules(CCommunicationControl& rControl) -{ - // Load the IPC modules - sdv::core::IModuleControl* pModuleControl = sdv::core::GetObject("ModuleControlService"); - ASSERT_NE(pModuleControl, nullptr); - EXPECT_NE(pModuleControl->Load("ipc_shared_mem.sdv"), 0u); - EXPECT_NE(pModuleControl->Load("core_ps.sdv"), 0u); - EXPECT_NE(pModuleControl->Load("UnitTest_IPC_Communication_ps.sdv"), 0u); - - // Create the services - sdv::core::IRepositoryControl* pRepositoryControl = sdv::core::GetObject("RepositoryService"); - ASSERT_NE(pRepositoryControl, nullptr); - EXPECT_NE(pRepositoryControl->CreateObject("DefaultSharedMemoryChannelControl", {}, {}), 0u); - - // Register the communication control service - sdv::core::IRegisterForeignObject* pRegisterObject = sdv::core::GetObject("RepositoryService"); - ASSERT_NE(pRegisterObject, nullptr); - EXPECT_NE(pRegisterObject->RegisterObject(&rControl, "CommunicationControl"), 0u); -} - -TEST(IPC_Communication_Test, Instantiate) +TEST(IPC_Communication_Test_SharedMem, Instantiate) { CCommunicationControl control; - EXPECT_NO_THROW(control.Initialize("")); + EXPECT_NO_THROW(control.Initialize(sdv::SObjectInfo())); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); EXPECT_NO_THROW(control.Shutdown()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::destruction_pending); } -TEST(IPC_Communication_Test, AssignServerEndpoint) +TEST(IPC_Communication_Test_SharedMem, AssignServerEndpoint) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -947,21 +31,24 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create a channel endpoint - sdv::ipc::ICreateEndpoint* ptrCreateEndpoint = sdv::core::GetObject("LocalChannelControl"); + sdv::ipc::ICreateEndpoint* ptrCreateEndpoint = sdv::core::GetObject("DefaultSharedMemory"); ASSERT_NE(ptrCreateEndpoint, nullptr); sdv::ipc::SChannelEndpoint sChannelEndpoint = ptrCreateEndpoint->CreateEndpoint(""); EXPECT_NE(sChannelEndpoint.pConnection, nullptr); @@ -979,7 +66,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, AssignClientEndpoint) +TEST(IPC_Communication_Test_SharedMem, AssignClientEndpoint) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -988,21 +75,24 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create a server endpoint - sdv::ipc::ICreateEndpoint* ptrCreateEndpoint = sdv::core::GetObject("LocalChannelControl"); + sdv::ipc::ICreateEndpoint* ptrCreateEndpoint = sdv::core::GetObject("DefaultSharedMemory"); ASSERT_NE(ptrCreateEndpoint, nullptr); sdv::ipc::SChannelEndpoint sChannelEndpoint = ptrCreateEndpoint->CreateEndpoint(""); EXPECT_NE(sChannelEndpoint.pConnection, nullptr); @@ -1011,7 +101,7 @@ ViewFilter = "Fatal" EXPECT_TRUE(ptrServerEndpoint); // Create a client endpoint - sdv::ipc::IChannelAccess* pChannelAccess = sdv::core::GetObject("LocalChannelControl"); + sdv::ipc::IChannelAccess* pChannelAccess = sdv::core::GetObject("DefaultSharedMemory"); ASSERT_NE(pChannelAccess, nullptr); sdv::TObjectPtr ptrClientEndpoint = pChannelAccess->Access(sChannelEndpoint.ssConnectString); EXPECT_TRUE(ptrClientEndpoint); @@ -1030,13 +120,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -// Disabled the following test due to an unidentified crash/heap corruption occurring with MINGW on the build-server. -// Bug-report #610009 describes this issue: https://dev.azure.com/SW4ZF/AZP-074_DivDI_SofDCarResearch/_workitems/edit/610009 -//#ifdef _WIN32 -TEST(IPC_Communication_Test, DISABLED_AssignClientEndpoint_Reconnect) -//#else -//TEST(IPC_Communication_Test, AssignClientEndpoint_Reconnect) -//#endif +TEST(IPC_Communication_Test_SharedMem, AssignClientEndpoint_Reconnect) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -1045,21 +129,24 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create a server endpoint - sdv::ipc::ICreateEndpoint* ptrCreateEndpoint = sdv::core::GetObject("LocalChannelControl"); + sdv::ipc::ICreateEndpoint* ptrCreateEndpoint = sdv::core::GetObject("DefaultSharedMemory"); ASSERT_NE(ptrCreateEndpoint, nullptr); sdv::ipc::SChannelEndpoint sChannelEndpoint = ptrCreateEndpoint->CreateEndpoint(""); EXPECT_NE(sChannelEndpoint.pConnection, nullptr); @@ -1068,7 +155,7 @@ ViewFilter = "Fatal" EXPECT_TRUE(ptrServerEndpoint); // Create a client endpoint - sdv::ipc::IChannelAccess* pChannelAccess = sdv::core::GetObject("LocalChannelControl"); + sdv::ipc::IChannelAccess* pChannelAccess = sdv::core::GetObject("DefaultSharedMemory"); ASSERT_NE(pChannelAccess, nullptr); sdv::TObjectPtr ptrClientEndpoint = pChannelAccess->Access(sChannelEndpoint.ssConnectString); EXPECT_TRUE(ptrClientEndpoint); @@ -1106,7 +193,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, AssignClientEndpoint_FailReconnect) +TEST(IPC_Communication_Test_SharedMem, AssignClientEndpoint_FailReconnect) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -1115,21 +202,24 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create a server endpoint - sdv::ipc::ICreateEndpoint* ptrCreateEndpoint = sdv::core::GetObject("LocalChannelControl"); + sdv::ipc::ICreateEndpoint* ptrCreateEndpoint = sdv::core::GetObject("DefaultSharedMemory"); ASSERT_NE(ptrCreateEndpoint, nullptr); sdv::ipc::SChannelEndpoint sChannelEndpoint = ptrCreateEndpoint->CreateEndpoint(""); EXPECT_NE(sChannelEndpoint.pConnection, nullptr); @@ -1138,7 +228,7 @@ ViewFilter = "Fatal" EXPECT_TRUE(ptrServerEndpoint); // Create a client endpoint - sdv::ipc::IChannelAccess* pChannelAccess = sdv::core::GetObject("LocalChannelControl"); + sdv::ipc::IChannelAccess* pChannelAccess = sdv::core::GetObject("DefaultSharedMemory"); ASSERT_NE(pChannelAccess, nullptr); sdv::TObjectPtr ptrClientEndpoint = pChannelAccess->Access(sChannelEndpoint.ssConnectString); EXPECT_TRUE(ptrClientEndpoint); @@ -1176,7 +266,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, CreateServerConnection) +TEST(IPC_Communication_Test_SharedMem, CreateServerConnection) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -1185,18 +275,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -1208,7 +301,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, CreateClientConnection) +TEST(IPC_Communication_Test_SharedMem, CreateClientConnection) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -1217,18 +310,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -1245,7 +341,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, CreateClientConnection_FailReconnect) +TEST(IPC_Communication_Test_SharedMem, CreateClientConnection_FailReconnect) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -1254,18 +350,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -1292,7 +391,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallClientToServer) +TEST(IPC_Communication_Test_SharedMem, MarshallClientToServer) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -1301,18 +400,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -1336,7 +438,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallInterfaceFromServer) +TEST(IPC_Communication_Test_SharedMem, MarshallInterfaceFromServer) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -1345,18 +447,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -1385,7 +490,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallInterfaceFromClient) +TEST(IPC_Communication_Test_SharedMem, MarshallInterfaceFromClient) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -1394,18 +499,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -1434,7 +542,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallGenericInterfaceFromServer) +TEST(IPC_Communication_Test_SharedMem, MarshallGenericInterfaceFromServer) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -1443,18 +551,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -1487,7 +598,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaStructBoolean) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaStructBoolean) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -1496,18 +607,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -1534,7 +648,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaStructIntegral) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaStructIntegral) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -1543,18 +657,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -1608,7 +725,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaStructCharacter) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaStructCharacter) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -1622,13 +739,13 @@ ViewFilter = "Fatal" // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -1660,7 +777,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaStructFloatingPoint) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaStructFloatingPoint) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -1669,18 +786,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -1713,7 +833,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaStructEnum) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaStructEnum) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -1722,18 +842,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -1759,7 +882,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaStructString) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaStructString) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -1768,18 +891,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -1823,7 +949,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaStructPointer) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaStructPointer) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -1832,18 +958,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -1898,7 +1027,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaStructSequence) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaStructSequence) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -1907,18 +1036,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -1973,7 +1105,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaStructInterface) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaStructInterface) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -1982,18 +1114,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -2036,7 +1171,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaStructSubCompound) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaStructSubCompound) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -2045,18 +1180,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -2086,7 +1224,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaStructBooleanArray) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaStructBooleanArray) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -2095,18 +1233,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -2135,7 +1276,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaStructIntegralArray) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaStructIntegralArray) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -2144,18 +1285,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -2239,7 +1383,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaStructCharacterArray) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaStructCharacterArray) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -2248,18 +1392,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -2299,7 +1446,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaStructFloatingPointArray) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaStructFloatingPointArray) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -2308,18 +1455,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -2358,7 +1508,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaStructEnumArray) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaStructEnumArray) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -2367,18 +1517,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -2406,7 +1559,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaStructStringArray) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaStructStringArray) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -2415,18 +1568,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -2490,7 +1646,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaStructPointerArray) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaStructPointerArray) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -2499,18 +1655,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -2596,7 +1755,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaStructSequenceArray) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaStructSequenceArray) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -2605,18 +1764,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -2702,7 +1864,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaStructInterfaceArray) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaStructInterfaceArray) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -2711,18 +1873,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -2775,7 +1940,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaStructSubCompoundArray) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaStructSubCompoundArray) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -2784,18 +1949,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -2833,7 +2001,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaStructBooleanTypedef) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaStructBooleanTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -2842,18 +2010,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -2880,7 +2051,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaStructIntegralTypedef) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaStructIntegralTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -2889,18 +2060,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -2954,7 +2128,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaStructCharacterTypedef) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaStructCharacterTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -2963,18 +2137,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -3006,7 +2183,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaStructFloatingPointTypedef) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaStructFloatingPointTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -3015,18 +2192,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -3059,7 +2239,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaStructEnumTypedef) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaStructEnumTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -3068,18 +2248,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -3105,7 +2288,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaStructStringTypedef) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaStructStringTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -3114,18 +2297,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -3170,7 +2356,7 @@ ViewFilter = "Fatal" } // Using typedefs of template based types produced an error: BUG #398266 -TEST(IPC_Communication_Test, DISABLED_MarshallMegaStructPointerTypedef) +TEST(IPC_Communication_Test_SharedMem, DISABLED_MarshallMegaStructPointerTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -3179,18 +2365,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -3246,7 +2435,7 @@ ViewFilter = "Fatal" } // Using typedefs of template based types produced an error: BUG #398266 -TEST(IPC_Communication_Test, DISABLED_MarshallMegaStructSequenceTypedef) +TEST(IPC_Communication_Test_SharedMem, DISABLED_MarshallMegaStructSequenceTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -3255,18 +2444,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -3321,7 +2513,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaStructInterfaceTypedef) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaStructInterfaceTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -3330,18 +2522,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -3386,7 +2581,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaStructSubCompoundTypedef) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaStructSubCompoundTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -3395,18 +2590,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -3438,7 +2636,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaTypeBasedUnionBoolean) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaTypeBasedUnionBoolean) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -3447,18 +2645,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -3486,7 +2687,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaTypeBasedUnionIntegral) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaTypeBasedUnionIntegral) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -3495,18 +2696,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -3589,7 +2793,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaTypeBasedUnionCharacter) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaTypeBasedUnionCharacter) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -3598,18 +2802,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -3648,7 +2855,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaTypeBasedUnionFloatingPoint) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaTypeBasedUnionFloatingPoint) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -3657,18 +2864,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -3707,7 +2917,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaTypeBasedUnionEnum) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaTypeBasedUnionEnum) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -3716,18 +2926,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -3754,7 +2967,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaTypeBasedUnionString) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaTypeBasedUnionString) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -3763,18 +2976,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -3837,7 +3053,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaTypeBasedUnionPointer) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaTypeBasedUnionPointer) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -3846,18 +3062,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -3919,7 +3138,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaTypeBasedUnionSequence) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaTypeBasedUnionSequence) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -3928,18 +3147,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -4001,7 +3223,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaTypeBasedUnionInterface) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaTypeBasedUnionInterface) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -4010,18 +3232,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -4069,7 +3294,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaTypeBasedUnionSubCompound) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaTypeBasedUnionSubCompound) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -4078,18 +3303,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -4116,7 +3344,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaTypeBasedUnionBooleanTypedef) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaTypeBasedUnionBooleanTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -4125,18 +3353,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -4164,7 +3395,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaTypeBasedUnionIntegralTypedef) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaTypeBasedUnionIntegralTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -4173,18 +3404,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -4267,7 +3501,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaTypeBasedUnionCharacterTypedef) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaTypeBasedUnionCharacterTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -4276,18 +3510,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -4326,7 +3563,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaTypeBasedUnionFloatingPointTypedef) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaTypeBasedUnionFloatingPointTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -4335,18 +3572,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -4385,7 +3625,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaTypeBasedUnionEnumTypedef) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaTypeBasedUnionEnumTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -4394,18 +3634,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -4432,7 +3675,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaTypeBasedUnionStringTypedef) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaTypeBasedUnionStringTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -4441,18 +3684,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -4516,7 +3762,7 @@ ViewFilter = "Fatal" } // Using typedefs of template based types produced an error: BUG #398266 -TEST(IPC_Communication_Test, DISABLED_MarshallMegaTypeBasedUnionPointerTypedef) +TEST(IPC_Communication_Test_SharedMem, DISABLED_MarshallMegaTypeBasedUnionPointerTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -4525,18 +3771,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -4599,7 +3848,7 @@ ViewFilter = "Fatal" } // Using typedefs of template based types produced an error: BUG #398266 -TEST(IPC_Communication_Test, DISABLED_MarshallMegaTypeBasedUnionSequenceTypedef) +TEST(IPC_Communication_Test_SharedMem, DISABLED_MarshallMegaTypeBasedUnionSequenceTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -4608,18 +3857,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -4681,7 +3933,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaTypeBasedUnionInterfaceTypedef) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaTypeBasedUnionInterfaceTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -4690,18 +3942,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -4750,7 +4005,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaTypeBasedUnionSubCompoundTypedef) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaTypeBasedUnionSubCompoundTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -4759,18 +4014,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -4801,7 +4059,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaVarBasedUnionBoolean) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaVarBasedUnionBoolean) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -4810,18 +4068,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -4849,7 +4110,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaVarBasedUnionIntegral) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaVarBasedUnionIntegral) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -4858,18 +4119,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -4952,7 +4216,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaVarBasedUnionCharacter) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaVarBasedUnionCharacter) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -4961,18 +4225,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -5011,7 +4278,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaVarBasedUnionFloatingPoint) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaVarBasedUnionFloatingPoint) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -5020,18 +4287,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -5070,7 +4340,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaVarBasedUnionEnum) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaVarBasedUnionEnum) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -5079,18 +4349,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -5117,7 +4390,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaVarBasedUnionString) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaVarBasedUnionString) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -5126,18 +4399,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -5200,7 +4476,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaVarBasedUnionPointer) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaVarBasedUnionPointer) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -5209,18 +4485,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -5282,7 +4561,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaVarBasedUnionSequence) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaVarBasedUnionSequence) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -5291,18 +4570,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -5364,7 +4646,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaVarBasedUnionInterface) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaVarBasedUnionInterface) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -5373,18 +4655,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -5432,7 +4717,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaVarBasedUnionSubCompound) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaVarBasedUnionSubCompound) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -5441,18 +4726,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -5479,7 +4767,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaVarBasedUnionBooleanTypedef) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaVarBasedUnionBooleanTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -5488,18 +4776,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -5527,7 +4818,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaVarBasedUnionIntegralTypedef) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaVarBasedUnionIntegralTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -5536,18 +4827,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -5630,7 +4924,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaVarBasedUnionCharacterTypedef) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaVarBasedUnionCharacterTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -5639,18 +4933,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -5689,7 +4986,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaVarBasedUnionFloatingPointTypedef) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaVarBasedUnionFloatingPointTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -5698,18 +4995,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -5748,7 +5048,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaVarBasedUnionEnumTypedef) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaVarBasedUnionEnumTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -5757,18 +5057,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -5795,7 +5098,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaVarBasedUnionStringTypedef) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaVarBasedUnionStringTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -5804,18 +5107,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -5879,7 +5185,7 @@ ViewFilter = "Fatal" } // Using typedefs of template based types produced an error: BUG #398266 -TEST(IPC_Communication_Test, DISABLED_MarshallMegaVarBasedUnionPointerTypedef) +TEST(IPC_Communication_Test_SharedMem, DISABLED_MarshallMegaVarBasedUnionPointerTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -5888,18 +5194,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -5962,7 +5271,7 @@ ViewFilter = "Fatal" } // Using typedefs of template based types produced an error: BUG #398266 -TEST(IPC_Communication_Test, DISABLED_MarshallMegaVarBasedUnionSequenceTypedef) +TEST(IPC_Communication_Test_SharedMem, DISABLED_MarshallMegaVarBasedUnionSequenceTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -5971,18 +5280,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -6044,7 +5356,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaVarBasedUnionInterfaceTypedef) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaVarBasedUnionInterfaceTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -6053,18 +5365,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; @@ -6113,7 +5428,7 @@ ViewFilter = "Fatal" appcontrol.Shutdown(); } -TEST(IPC_Communication_Test, MarshallMegaVarBasedUnionSubCompoundTypedef) +TEST(IPC_Communication_Test_SharedMem, MarshallMegaVarBasedUnionSubCompoundTypedef) { sdv::app::CAppControl appcontrol; ASSERT_TRUE(appcontrol.Startup(R"config( @@ -6122,18 +5437,21 @@ Mode = "Essential" [LogHandler] ViewFilter = "Fatal" + +[Console] +Report = "Silent" )config")); ASSERT_TRUE(appcontrol.IsRunning()); // Start communication control CCommunicationControl control; - control.Initialize(""); + control.Initialize(sdv::SObjectInfo()); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); control.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); // Load the shared memory components - LoadIPCModules(control); + LoadIPCModules(control, ELocalIpcBackend::sharedmem); // Create the server connection CInterfaceTest test; diff --git a/tests/unit_tests/ipc_com/ipc_com_uds.cpp b/tests/unit_tests/ipc_com/ipc_com_uds.cpp new file mode 100644 index 0000000..8c6f472 --- /dev/null +++ b/tests/unit_tests/ipc_com/ipc_com_uds.cpp @@ -0,0 +1,5476 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + + +#include "include.h" + +TEST(IPC_Communication_Test_UDS, AssignServerEndpoint) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create a channel endpoint + sdv::ipc::ICreateEndpoint* ptrCreateEndpoint = sdv::core::GetObject("unix_domain_sockets"); + ASSERT_NE(ptrCreateEndpoint, nullptr); + sdv::ipc::SChannelEndpoint sChannelEndpoint = ptrCreateEndpoint->CreateEndpoint(""); + EXPECT_NE(sChannelEndpoint.pConnection, nullptr); + EXPECT_FALSE(sChannelEndpoint.ssConnectString.empty()); + EXPECT_NE(sChannelEndpoint.ssConnectString.find("proto=uds"), std::string::npos); + sdv::TObjectPtr ptrEndpoint = sChannelEndpoint.pConnection; // Automatic lifetime management + EXPECT_TRUE(ptrEndpoint); + + // Assign the server endpoint + CInterfaceTest test; + EXPECT_TRUE(control.AssignServerEndpoint(ptrEndpoint, &test, 100, false) != 0u); + ptrEndpoint.Clear(); // Lifetime taken over by communication control. + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, AssignClientEndpoint) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create a server endpoint + sdv::ipc::ICreateEndpoint* ptrCreateEndpoint = sdv::core::GetObject("unix_domain_sockets"); + ASSERT_NE(ptrCreateEndpoint, nullptr); + sdv::ipc::SChannelEndpoint sChannelEndpoint = ptrCreateEndpoint->CreateEndpoint(""); + EXPECT_NE(sChannelEndpoint.pConnection, nullptr); + EXPECT_FALSE(sChannelEndpoint.ssConnectString.empty()); + sdv::TObjectPtr ptrServerEndpoint = sChannelEndpoint.pConnection; // Automatic lifetime management + EXPECT_TRUE(ptrServerEndpoint); + + // Create a client endpoint + sdv::ipc::IChannelAccess* pChannelAccess = sdv::core::GetObject("unix_domain_sockets"); + ASSERT_NE(pChannelAccess, nullptr); + sdv::TObjectPtr ptrClientEndpoint = pChannelAccess->Access(sChannelEndpoint.ssConnectString); + EXPECT_TRUE(ptrClientEndpoint); + + // Assign the server and client endpoints + CInterfaceTest test; + EXPECT_TRUE(control.AssignServerEndpoint(ptrServerEndpoint, &test, 100, false) != 0u); + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.AssignClientEndpoint(ptrClientEndpoint, 1000, pObjectProxy) != 0u); + EXPECT_NE(pObjectProxy, nullptr); + ptrServerEndpoint.Clear(); // Lifetime taken over by communication control. + ptrClientEndpoint.Clear(); // Lifetime taken over by communication control. + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, AssignClientEndpoint_Reconnect) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create a server endpoint + sdv::ipc::ICreateEndpoint* ptrCreateEndpoint = sdv::core::GetObject("unix_domain_sockets"); + ASSERT_NE(ptrCreateEndpoint, nullptr); + sdv::ipc::SChannelEndpoint sChannelEndpoint = ptrCreateEndpoint->CreateEndpoint(""); + EXPECT_NE(sChannelEndpoint.pConnection, nullptr); + EXPECT_FALSE(sChannelEndpoint.ssConnectString.empty()); + sdv::TObjectPtr ptrServerEndpoint = sChannelEndpoint.pConnection; // Automatic lifetime management + EXPECT_TRUE(ptrServerEndpoint); + + // Create a client endpoint + sdv::ipc::IChannelAccess* pChannelAccess = sdv::core::GetObject("unix_domain_sockets"); + ASSERT_NE(pChannelAccess, nullptr); + sdv::TObjectPtr ptrClientEndpoint = pChannelAccess->Access(sChannelEndpoint.ssConnectString); + EXPECT_TRUE(ptrClientEndpoint); + + // Assign the server and client endpoints + CInterfaceTest test; + EXPECT_TRUE(control.AssignServerEndpoint(ptrServerEndpoint, &test, 100, true) != 0u); + ptrServerEndpoint.Clear(); // Lifetime taken over by communication control. + sdv::IInterfaceAccess* pObjectProxy = nullptr; + sdv::com::TConnectionID tConnectionID = control.AssignClientEndpoint(ptrClientEndpoint, 1000, pObjectProxy); + EXPECT_TRUE(tConnectionID != 0u); + EXPECT_NE(pObjectProxy, nullptr); + + // Disconnect from the client + sdv::ipc::IConnect* pClientConnect = ptrClientEndpoint.GetInterface(); + ASSERT_NE(pClientConnect, nullptr); + EXPECT_EQ(pClientConnect->GetConnectState(), sdv::ipc::EConnectState::connected); + control.RemoveConnection(tConnectionID); + EXPECT_EQ(pClientConnect->GetConnectState(), sdv::ipc::EConnectState::disconnected); + ptrClientEndpoint.Clear(); // Lifetime taken over by communication control. + + // Create another client endpoint + ptrClientEndpoint = pChannelAccess->Access(sChannelEndpoint.ssConnectString); + EXPECT_TRUE(ptrClientEndpoint); + + // Assign the new client endpoint + pObjectProxy = nullptr; + tConnectionID = control.AssignClientEndpoint(ptrClientEndpoint, 1000, pObjectProxy); + EXPECT_TRUE(tConnectionID != 0u); + EXPECT_NE(pObjectProxy, nullptr); + ptrClientEndpoint.Clear(); // Lifetime taken over by communication control. + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, AssignClientEndpoint_FailReconnect) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create a server endpoint + sdv::ipc::ICreateEndpoint* ptrCreateEndpoint = sdv::core::GetObject("unix_domain_sockets"); + ASSERT_NE(ptrCreateEndpoint, nullptr); + sdv::ipc::SChannelEndpoint sChannelEndpoint = ptrCreateEndpoint->CreateEndpoint(""); + EXPECT_NE(sChannelEndpoint.pConnection, nullptr); + EXPECT_FALSE(sChannelEndpoint.ssConnectString.empty()); + sdv::TObjectPtr ptrServerEndpoint = sChannelEndpoint.pConnection; // Automatic lifetime management + EXPECT_TRUE(ptrServerEndpoint); + + // Create a client endpoint + sdv::ipc::IChannelAccess* pChannelAccess = sdv::core::GetObject("unix_domain_sockets"); + ASSERT_NE(pChannelAccess, nullptr); + sdv::TObjectPtr ptrClientEndpoint = pChannelAccess->Access(sChannelEndpoint.ssConnectString); + EXPECT_TRUE(ptrClientEndpoint); + + // Assign the server and client endpoints + CInterfaceTest test; + EXPECT_TRUE(control.AssignServerEndpoint(ptrServerEndpoint, &test, 100, false) != 0u); + ptrServerEndpoint.Clear(); // Lifetime taken over by communication control. + sdv::IInterfaceAccess* pObjectProxy = nullptr; + sdv::com::TConnectionID tConnectionID = control.AssignClientEndpoint(ptrClientEndpoint, 1000, pObjectProxy); + EXPECT_TRUE(tConnectionID != 0u); + EXPECT_NE(pObjectProxy, nullptr); + + // Disconnect from the client + sdv::ipc::IConnect* pClientConnect = ptrClientEndpoint.GetInterface(); + ASSERT_NE(pClientConnect, nullptr); + EXPECT_EQ(pClientConnect->GetConnectState(), sdv::ipc::EConnectState::connected); + control.RemoveConnection(tConnectionID); + EXPECT_EQ(pClientConnect->GetConnectState(), sdv::ipc::EConnectState::disconnected); + ptrClientEndpoint.Clear(); // Lifetime taken over by communication control. + + // Create another client endpoint + ptrClientEndpoint = pChannelAccess->Access(sChannelEndpoint.ssConnectString); + EXPECT_TRUE(ptrClientEndpoint); + + // Assign the new client endpoint - this should fail! + pObjectProxy = nullptr; + tConnectionID = control.AssignClientEndpoint(ptrClientEndpoint, 1000, pObjectProxy); + EXPECT_TRUE(tConnectionID == 0u); + EXPECT_EQ(pObjectProxy, nullptr); + ptrClientEndpoint.Clear(); // Lifetime taken over by communication control. + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, CreateServerConnection) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, CreateClientConnection) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + EXPECT_NE(pObjectProxy, nullptr); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, CreateClientConnection_FailReconnect) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + sdv::com::TConnectionID tConnectionID = control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy); + EXPECT_TRUE(tConnectionID != 0u); + EXPECT_NE(pObjectProxy, nullptr); + + // Disconnect from the client + control.RemoveConnection(tConnectionID); + + // Create a new client connection + pObjectProxy = nullptr; + tConnectionID = control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy); + EXPECT_TRUE(tConnectionID == 0u); + EXPECT_EQ(pObjectProxy, nullptr); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallClientToServer) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Request the ISayHello interface + ISayHello* pSayHello = nullptr; + EXPECT_NO_THROW(pSayHello = pObjectProxy->GetInterface()); + ASSERT_NE(pSayHello, nullptr); + + EXPECT_EQ(pSayHello->Hello(), "Hello"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallInterfaceFromServer) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Request the IRequestHello interface + IRequestHello* pRequestHello = nullptr; + EXPECT_NO_THROW(pRequestHello = pObjectProxy->GetInterface()); + ASSERT_NE(pRequestHello, nullptr); + + // Request the ISayHello interface + ISayHello* pSayHello = pRequestHello->Request(); + + // Call the interface as if talking from the client + ASSERT_NE(pSayHello, nullptr); + EXPECT_EQ(pSayHello->Hello(), "Hello"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallInterfaceFromClient) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Request the IRegisterHelloCallback interface + IRegisterHelloCallback* pRegisterHelloCallback = nullptr; + EXPECT_NO_THROW(pRegisterHelloCallback = pObjectProxy->GetInterface()); + ASSERT_NE(pRegisterHelloCallback, nullptr); + + // Register the callback (stored in the object) + pRegisterHelloCallback->Register(&test); + + // Call the interface as if talking from the server + ASSERT_NE(test.m_pHello, nullptr); + EXPECT_EQ(test.m_pHello->Hello(), "Hello"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallGenericInterfaceFromServer) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Request the IRegisterHelloCallback interface + sdv::IInterfaceAccess* pRegisterHelloCallback = nullptr; + EXPECT_NO_THROW(pRegisterHelloCallback = pObjectProxy->GetInterface()); + ASSERT_NE(pRegisterHelloCallback, nullptr); + + // Call GetInterface to get the IInterfaceAccess pointer once more (to be able to test marshalling). + sdv::IInterfaceAccess* pInterfaceAccess = pObjectProxy->GetInterface(); + ASSERT_NE(pInterfaceAccess, nullptr); + + // Request the pSayHello interface (this will be marshalled as well) + ISayHello* pSayHello = pInterfaceAccess->GetInterface(); + + // Call the interface as if talking from the client + ASSERT_NE(pSayHello, nullptr); + EXPECT_EQ(pSayHello->Hello(), "Hello"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaStructBoolean) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.bVal = false; + EXPECT_FALSE(sStruct.bVal); + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_TRUE(sStruct.bVal); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaStructIntegral) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.sVal = -10; + sStruct.usVal = 10; + sStruct.lVal = -20; + sStruct.ulVal = 20; + sStruct.llVal = -30; + sStruct.ullVal = 30; + sStruct.i8Val = -40; + sStruct.ui8Val = 40; + sStruct.i16Val = -50; + sStruct.ui16Val = 50; + sStruct.i32Val = -60; + sStruct.ui32Val = 60; + sStruct.i64Val = -70; + sStruct.ui64Val = 70; + sStruct.nVal = 80; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.sVal, -11); + EXPECT_EQ(sStruct.usVal, 11u); + EXPECT_EQ(sStruct.lVal, -21); + EXPECT_EQ(sStruct.ulVal, 21u); + EXPECT_EQ(sStruct.llVal, -31); + EXPECT_EQ(sStruct.ullVal, 31u); + EXPECT_EQ(static_cast(sStruct.i8Val), static_cast(-41)); // Cast needed for ARM compatibility + EXPECT_EQ(sStruct.ui8Val, 41u); + EXPECT_EQ(sStruct.i16Val, -51); + EXPECT_EQ(sStruct.ui16Val, 51u); + EXPECT_EQ(sStruct.i32Val, -61); + EXPECT_EQ(sStruct.ui32Val, 61u); + EXPECT_EQ(sStruct.i64Val, -71); + EXPECT_EQ(sStruct.ui64Val, 71u); + EXPECT_EQ(sStruct.nVal, 81); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaStructCharacter) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.cVal = 'A'; + sStruct.c16Val = u'D'; + sStruct.c32Val = U'G'; + sStruct.wcVal = L'J'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.cVal, 'B'); + EXPECT_EQ(sStruct.c16Val, u'E'); + EXPECT_EQ(sStruct.c32Val, U'H'); + EXPECT_EQ(sStruct.wcVal, L'K'); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaStructFloatingPoint) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + float fVal = -1234.5678f; + sStruct.fVal = fVal; + double dVal = 8765.4321; + sStruct.dVal = dVal; + long double ldVal = -1234.4321; + sStruct.ldVal = ldVal; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.fVal, fVal - 1000.000f); + EXPECT_EQ(sStruct.dVal, dVal + 1000.000); + EXPECT_EQ(sStruct.ldVal, ldVal - 1000.000); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaStructEnum) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.eHelloVal = EHello::hello; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.eHelloVal, EHello::hallo); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaStructString) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.ssVal = "hi"; + sStruct.ss8Val = "huhu"; + sStruct.ss16Val = u"hey"; + sStruct.ss32Val = U"hello"; + sStruct.wssVal = L"servus"; + sStruct.ssFixVal = "hi"; + sStruct.ss8FixVal = "huhu"; + sStruct.ss16FixVal = u"hey"; + sStruct.ss32FixVal = U"hello"; + sStruct.wssFixVal = L"servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.ssVal, "hij"); + EXPECT_EQ(sStruct.ss8Val, "huhuv"); + EXPECT_EQ(sStruct.ss16Val, u"heyz"); + EXPECT_EQ(sStruct.ss32Val, U"hellop"); + EXPECT_EQ(sStruct.wssVal, L"servust"); + EXPECT_EQ(sStruct.ssFixVal, "hij"); + EXPECT_EQ(sStruct.ss8FixVal, "huhuv"); + EXPECT_EQ(sStruct.ss16FixVal, u"heyz"); + EXPECT_EQ(sStruct.ss32FixVal, U"hellop"); + EXPECT_EQ(sStruct.wssFixVal, L"servust"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaStructPointer) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.ptr8Val.resize(100); + for (size_t nIndex = 0; nIndex < sStruct.ptr8Val.size(); nIndex++) + sStruct.ptr8Val[nIndex] = static_cast(nIndex); + sStruct.ptrssVal.resize(3); + sStruct.ptrssVal[0] = "hello"; + sStruct.ptrssVal[1] = "huhu"; + sStruct.ptrssVal[2] = "hi"; + sStruct.ptr8FixVal.resize(sStruct.ptr8FixVal.capacity()); + for (size_t nIndex = 0; nIndex < sStruct.ptr8FixVal.size(); nIndex++) + sStruct.ptr8FixVal[nIndex] = static_cast(nIndex); + sStruct.ptrssFixVal.resize(sStruct.ptrssFixVal.capacity()); + sStruct.ptrssFixVal[0] = "hi"; + sStruct.ptrssFixVal[1] = "hoi"; + sStruct.ptrssFixVal[2] = "hello"; + sStruct.ptrssFixVal[3] = "hallo"; + sStruct.ptrssFixVal[4] = "servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.ptr8Val.size(), 200); + for (size_t nIndex = 0; nIndex < sStruct.ptr8Val.size() / 2; nIndex++) + EXPECT_EQ(sStruct.ptr8Val[nIndex], sStruct.ptr8Val[nIndex + sStruct.ptr8Val.size() / 2]); + EXPECT_EQ(sStruct.ptrssVal.size(), 6); + for (size_t nIndex = 0; nIndex < sStruct.ptrssVal.size() / 2; nIndex++) + EXPECT_EQ(sStruct.ptrssVal[nIndex], sStruct.ptrssVal[nIndex + sStruct.ptrssVal.size() / 2]); + EXPECT_EQ(sStruct.ptr8FixVal.size(), 20); + for (size_t nIndex = 0; nIndex < sStruct.ptr8FixVal.size(); nIndex++) + EXPECT_EQ(sStruct.ptr8FixVal[nIndex], static_cast(nIndex << 1)); + EXPECT_EQ(sStruct.ptrssFixVal.size(), 5); + EXPECT_EQ(sStruct.ptrssFixVal[0], "hihi"); + EXPECT_EQ(sStruct.ptrssFixVal[1], "hoihoi"); + EXPECT_EQ(sStruct.ptrssFixVal[2], "hellohello"); + EXPECT_EQ(sStruct.ptrssFixVal[3], "hallohallo"); + EXPECT_EQ(sStruct.ptrssFixVal[4], "servusservus"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaStructSequence) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.seq8Val.resize(100); + for (size_t nIndex = 0; nIndex < sStruct.seq8Val.size(); nIndex++) + sStruct.seq8Val[nIndex] = static_cast(nIndex); + sStruct.seqssVal.resize(3); + sStruct.seqssVal[0] = "hello"; + sStruct.seqssVal[1] = "huhu"; + sStruct.seqssVal[2] = "hi"; + sStruct.seq8FixVal.resize(sStruct.seq8FixVal.capacity()); + for (size_t nIndex = 0; nIndex < sStruct.seq8FixVal.size(); nIndex++) + sStruct.seq8FixVal[nIndex] = static_cast(nIndex); + sStruct.seqssFixVal.resize(sStruct.seqssFixVal.capacity()); + sStruct.seqssFixVal[0] = "hi"; + sStruct.seqssFixVal[1] = "hoi"; + sStruct.seqssFixVal[2] = "hello"; + sStruct.seqssFixVal[3] = "hallo"; + sStruct.seqssFixVal[4] = "servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.seq8Val.size(), 200); + for (size_t nIndex = 0; nIndex < sStruct.seq8Val.size() / 2; nIndex++) + EXPECT_EQ(sStruct.seq8Val[nIndex], sStruct.seq8Val[nIndex + sStruct.seq8Val.size() / 2]); + EXPECT_EQ(sStruct.seqssVal.size(), 6); + for (size_t nIndex = 0; nIndex < sStruct.seqssVal.size() / 2; nIndex++) + EXPECT_EQ(sStruct.seqssVal[nIndex], sStruct.seqssVal[nIndex + sStruct.seqssVal.size() / 2]); + EXPECT_EQ(sStruct.seq8FixVal.size(), 20); + for (size_t nIndex = 0; nIndex < sStruct.seq8FixVal.size(); nIndex++) + EXPECT_EQ(sStruct.seq8FixVal[nIndex], static_cast(nIndex << 1)); + EXPECT_EQ(sStruct.seqssFixVal.size(), 5); + EXPECT_EQ(sStruct.seqssFixVal[0], "hihi"); + EXPECT_EQ(sStruct.seqssFixVal[1], "hoihoi"); + EXPECT_EQ(sStruct.seqssFixVal[2], "hellohello"); + EXPECT_EQ(sStruct.seqssFixVal[3], "hallohallo"); + EXPECT_EQ(sStruct.seqssFixVal[4], "servusservus"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaStructInterface) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + struct SMultiply : public IMultiplyValue + { + size_t Multiply(size_t n) + { + return n * n; + } + } sMultiply; + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + + sStruct.idVal = sdv::GetInterfaceId(); + sStruct.ifcVal = static_cast(&sMultiply); + sStruct.pMultiplyValue = &sMultiply; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.idVal, sdv::GetInterfaceId()); + EXPECT_EQ(sStruct.ifcVal.id(), sdv::GetInterfaceId()); + ASSERT_NE(sStruct.ifcVal, nullptr); + ASSERT_NE(sStruct.ifcVal.get(), nullptr); + EXPECT_EQ(sStruct.ifcVal.get()->Add(99), 19602); // 99 * 99 + 99 * 99 + ASSERT_NE(sStruct.pMultiplyValue, nullptr); + EXPECT_EQ(sStruct.pMultiplyValue->Multiply(5), 625); // 5 * 5 * 5 * 5 + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaStructSubCompound) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.sSubVal.i = 1; + sStruct.sIndVal.i = 10; + //sStruct.sUnnamedVal.i = 100; // 12.04.2024 EVE Unnamed structures are not supported by IDL compiler. + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.sSubVal.i, 2); + EXPECT_EQ(sStruct.sIndVal.i, 20); + //EXPECT_EQ(sStruct.sUnnamedVal.i, 120); // 12.04.2024 EVE Unnamed structures are not supported by IDL compiler. + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaStructBooleanArray) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.rgbVal[0] = false; + sStruct.rgbVal[1] = true; + EXPECT_FALSE(sStruct.bVal); + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_TRUE(sStruct.rgbVal[0]); + EXPECT_FALSE(sStruct.rgbVal[1]); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaStructIntegralArray) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.rgsVal[0] = -10; + sStruct.rgsVal[1] = -11; + sStruct.rgusVal[0] = 10; + sStruct.rgusVal[1] = 11; + sStruct.rglVal[0] = -20; + sStruct.rglVal[1] = -21; + sStruct.rgulVal[0] = 20; + sStruct.rgulVal[1] = 21; + sStruct.rgllVal[0] = -30; + sStruct.rgllVal[1] = -31; + sStruct.rgullVal[0] = 30; + sStruct.rgullVal[1] = 31; + sStruct.rgi8Val[0] = -40; + sStruct.rgi8Val[1] = -41; + sStruct.rgui8Val[0] = 40; + sStruct.rgui8Val[1] = 41; + sStruct.rgi16Val[0] = -50; + sStruct.rgi16Val[1] = -51; + sStruct.rgui16Val[0] = 50; + sStruct.rgui16Val[1] = 51; + sStruct.rgi32Val[0] = -60; + sStruct.rgi32Val[1] = -61; + sStruct.rgui32Val[0] = 60; + sStruct.rgui32Val[1] = 61; + sStruct.rgi64Val[0] = -70; + sStruct.rgi64Val[1] = -71; + sStruct.rgui64Val[0] = 70; + sStruct.rgui64Val[1] = 71; + sStruct.rgnVal[0] = 80; + sStruct.rgnVal[1] = 81; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.rgsVal[0], -11); + EXPECT_EQ(sStruct.rgsVal[1], -10); + EXPECT_EQ(sStruct.rgusVal[0], 11u); + EXPECT_EQ(sStruct.rgusVal[1], 13u); + EXPECT_EQ(sStruct.rglVal[0], -21); + EXPECT_EQ(sStruct.rglVal[1], -20); + EXPECT_EQ(sStruct.rgulVal[0], 21u); + EXPECT_EQ(sStruct.rgulVal[1], 23u); + EXPECT_EQ(sStruct.rgllVal[0], -31); + EXPECT_EQ(sStruct.rgllVal[1], -30); + EXPECT_EQ(sStruct.rgullVal[0], 31u); + EXPECT_EQ(sStruct.rgullVal[1], 33u); + EXPECT_EQ(static_cast(sStruct.rgi8Val[0]), static_cast(-41)); // Cast needed for ARM compatibility + EXPECT_EQ(static_cast(sStruct.rgi8Val[1]), static_cast(-40)); // Cast needed for ARM compatibility + EXPECT_EQ(sStruct.rgui8Val[0], 41u); + EXPECT_EQ(sStruct.rgui8Val[1], 43u); + EXPECT_EQ(sStruct.rgi16Val[0], -51); + EXPECT_EQ(sStruct.rgi16Val[1], -50); + EXPECT_EQ(sStruct.rgui16Val[0], 51u); + EXPECT_EQ(sStruct.rgui16Val[1], 53u); + EXPECT_EQ(sStruct.rgi32Val[0], -61); + EXPECT_EQ(sStruct.rgi32Val[1], -60); + EXPECT_EQ(sStruct.rgui32Val[0], 61u); + EXPECT_EQ(sStruct.rgui32Val[1], 63u); + EXPECT_EQ(sStruct.rgi64Val[0], -71); + EXPECT_EQ(sStruct.rgi64Val[1], -70); + EXPECT_EQ(sStruct.rgui64Val[0], 71u); + EXPECT_EQ(sStruct.rgui64Val[1], 73u); + EXPECT_EQ(sStruct.rgnVal[0], 81); + EXPECT_EQ(sStruct.rgnVal[1], 83); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaStructCharacterArray) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.rgcVal[0] = 'A'; + sStruct.rgcVal[1] = 'B'; + sStruct.rgc16Val[0] = u'D'; + sStruct.rgc16Val[1] = u'E'; + sStruct.rgc32Val[0] = U'G'; + sStruct.rgc32Val[1] = U'H'; + sStruct.rgwcVal[0] = L'J'; + sStruct.rgwcVal[1] = L'K'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.rgcVal[0], 'B'); + EXPECT_EQ(sStruct.rgcVal[1], 'D'); + EXPECT_EQ(sStruct.rgc16Val[0], u'E'); + EXPECT_EQ(sStruct.rgc16Val[1], u'G'); + EXPECT_EQ(sStruct.rgc32Val[0], U'H'); + EXPECT_EQ(sStruct.rgc32Val[1], U'J'); + EXPECT_EQ(sStruct.rgwcVal[0], L'K'); + EXPECT_EQ(sStruct.rgwcVal[1], L'M'); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaStructFloatingPointArray) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + float fVal = -1234.5678f; + sStruct.rgfVal[0] = fVal; + sStruct.rgfVal[1] = fVal; + double dVal = 8765.4321; + sStruct.rgdVal[0] = dVal; + sStruct.rgdVal[1] = dVal; + long double ldVal = -1234.4321; + sStruct.rgldVal[0] = ldVal; + sStruct.rgldVal[1] = ldVal; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.rgfVal[0], fVal - 1000.000f); + EXPECT_EQ(sStruct.rgfVal[1], fVal + 2000.000f); + EXPECT_EQ(sStruct.rgdVal[0], dVal + 1000.000); + EXPECT_EQ(sStruct.rgdVal[1], dVal - 2000.000); + EXPECT_EQ(sStruct.rgldVal[0], ldVal - 1000.000); + EXPECT_EQ(sStruct.rgldVal[1], ldVal + 2000.000); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaStructEnumArray) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.rgeHelloVal[0] = EHello::hello; + sStruct.rgeHelloVal[1] = EHello::hello; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.rgeHelloVal[0], EHello::hallo); + EXPECT_EQ(sStruct.rgeHelloVal[1], EHello::huhu); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaStructStringArray) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.rgssVal[0] = "hi"; + sStruct.rgssVal[1] = "hi"; + sStruct.rgss8Val[0] = "huhu"; + sStruct.rgss8Val[1] = "huhu"; + sStruct.rgss16Val[0] = u"hey"; + sStruct.rgss16Val[1] = u"hey"; + sStruct.rgss32Val[0] = U"hello"; + sStruct.rgss32Val[1] = U"hello"; + sStruct.rgwssVal[0] = L"servus"; + sStruct.rgwssVal[1] = L"servus"; + sStruct.rgssFixVal[0] = "hi"; + sStruct.rgssFixVal[1] = "hi"; + sStruct.rgss8FixVal[0] = "huhu"; + sStruct.rgss8FixVal[1] = "huhu"; + sStruct.rgss16FixVal[0] = u"hey"; + sStruct.rgss16FixVal[1] = u"hey"; + sStruct.rgss32FixVal[0] = U"hello"; + sStruct.rgss32FixVal[1] = U"hello"; + sStruct.rgwssFixVal[0] = L"servus"; + sStruct.rgwssFixVal[1] = L"servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.rgssVal[0], "hij"); + EXPECT_EQ(sStruct.rgssVal[1], "hii"); + EXPECT_EQ(sStruct.rgss8Val[0], "huhuv"); + EXPECT_EQ(sStruct.rgss8Val[1], "huhui"); + EXPECT_EQ(sStruct.rgss16Val[0], u"heyz"); + EXPECT_EQ(sStruct.rgss16Val[1], u"heyi"); + EXPECT_EQ(sStruct.rgss32Val[0], U"hellop"); + EXPECT_EQ(sStruct.rgss32Val[1], U"helloi"); + EXPECT_EQ(sStruct.rgwssVal[0], L"servust"); + EXPECT_EQ(sStruct.rgwssVal[1], L"servust"); + EXPECT_EQ(sStruct.rgssFixVal[0], "hij"); + EXPECT_EQ(sStruct.rgssFixVal[1], "hii"); + EXPECT_EQ(sStruct.rgss8FixVal[0], "huhuv"); + EXPECT_EQ(sStruct.rgss8FixVal[1], "huhui"); + EXPECT_EQ(sStruct.rgss16FixVal[0], u"heyz"); + EXPECT_EQ(sStruct.rgss16FixVal[1], u"heyi"); + EXPECT_EQ(sStruct.rgss32FixVal[0], U"hellop"); + EXPECT_EQ(sStruct.rgss32FixVal[1], U"helloi"); + EXPECT_EQ(sStruct.rgwssFixVal[0], L"servust"); + EXPECT_EQ(sStruct.rgwssFixVal[1], L"servust"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaStructPointerArray) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.rgptr8Val[0].resize(100); + sStruct.rgptr8Val[1].resize(100); + for (size_t nIndex = 0; nIndex < sStruct.rgptr8Val[0].size(); nIndex++) + sStruct.rgptr8Val[0][nIndex] = static_cast(nIndex); + for (size_t nIndex = 0; nIndex < sStruct.rgptr8Val[1].size(); nIndex++) + sStruct.rgptr8Val[1][nIndex] = static_cast(nIndex); + sStruct.rgptrssVal[0].resize(3); + sStruct.rgptrssVal[1].resize(3); + sStruct.rgptrssVal[0][0] = "hello"; + sStruct.rgptrssVal[1][0] = "ohhello"; + sStruct.rgptrssVal[0][1] = "huhu"; + sStruct.rgptrssVal[1][1] = "ohhuhu"; + sStruct.rgptrssVal[0][2] = "hi"; + sStruct.rgptrssVal[1][2] = "ohhi"; + sStruct.rgptr8FixVal[0].resize(sStruct.rgptr8FixVal[0].capacity()); + sStruct.rgptr8FixVal[1].resize(sStruct.rgptr8FixVal[1].capacity()); + for (size_t nIndex = 0; nIndex < sStruct.rgptr8FixVal[0].size(); nIndex++) + sStruct.rgptr8FixVal[0][nIndex] = static_cast(nIndex); + for (size_t nIndex = 0; nIndex < sStruct.rgptr8FixVal[1].size(); nIndex++) + sStruct.rgptr8FixVal[1][nIndex] = static_cast(nIndex); + sStruct.rgptrssFixVal[0].resize(sStruct.rgptrssFixVal[0].capacity()); + sStruct.rgptrssFixVal[1].resize(sStruct.rgptrssFixVal[1].capacity()); + sStruct.rgptrssFixVal[0][0] = "hi"; + sStruct.rgptrssFixVal[1][0] = "ohhi"; + sStruct.rgptrssFixVal[0][1] = "hoi"; + sStruct.rgptrssFixVal[1][1] = "ohhoi"; + sStruct.rgptrssFixVal[0][2] = "hello"; + sStruct.rgptrssFixVal[1][2] = "ohhello"; + sStruct.rgptrssFixVal[0][3] = "hallo"; + sStruct.rgptrssFixVal[1][3] = "ohhallo"; + sStruct.rgptrssFixVal[0][4] = "servus"; + sStruct.rgptrssFixVal[1][4] = "ohservus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.rgptr8Val[0].size(), 200); + EXPECT_EQ(sStruct.rgptr8Val[1].size(), 300); + for (size_t nIndex = 0; nIndex < sStruct.rgptr8Val[0].size() / 2; nIndex++) + EXPECT_EQ(sStruct.rgptr8Val[0][nIndex], sStruct.rgptr8Val[0][nIndex + sStruct.rgptr8Val[0].size() / 2]); + for (size_t nIndex = 0; nIndex < sStruct.rgptr8Val[1].size() / 3; nIndex++) + EXPECT_EQ(sStruct.rgptr8Val[1][nIndex], sStruct.rgptr8Val[1][nIndex + sStruct.rgptr8Val[1].size() / 3]); + EXPECT_EQ(sStruct.rgptrssVal[0].size(), 6); + EXPECT_EQ(sStruct.rgptrssVal[1].size(), 9); + for (size_t nIndex = 0; nIndex < sStruct.rgptrssVal[0].size() / 2; nIndex++) + EXPECT_EQ(sStruct.rgptrssVal[0][nIndex], sStruct.rgptrssVal[0][nIndex + sStruct.rgptrssVal[0].size() / 2]); + for (size_t nIndex = 0; nIndex < sStruct.rgptrssVal[1].size() / 3; nIndex++) + EXPECT_EQ(sStruct.rgptrssVal[1][nIndex], sStruct.rgptrssVal[1][nIndex + sStruct.rgptrssVal[1].size() / 3]); + EXPECT_EQ(sStruct.rgptr8FixVal[0].size(), 20); + EXPECT_EQ(sStruct.rgptr8FixVal[1].size(), 20); + for (size_t nIndex = 0; nIndex < sStruct.rgptr8FixVal[0].size(); nIndex++) + EXPECT_EQ(sStruct.rgptr8FixVal[0][nIndex], static_cast(nIndex << 1)); + for (size_t nIndex = 0; nIndex < sStruct.rgptr8FixVal[1].size(); nIndex++) + EXPECT_EQ(sStruct.rgptr8FixVal[1][nIndex], static_cast(nIndex << 1)); + EXPECT_EQ(sStruct.rgptrssFixVal[0].size(), 5); + EXPECT_EQ(sStruct.rgptrssFixVal[1].size(), 5); + EXPECT_EQ(sStruct.rgptrssFixVal[0][0], "hihi"); + EXPECT_EQ(sStruct.rgptrssFixVal[1][0], "ohhiohhi"); + EXPECT_EQ(sStruct.rgptrssFixVal[0][1], "hoihoi"); + EXPECT_EQ(sStruct.rgptrssFixVal[1][1], "ohhoiohhoi"); + EXPECT_EQ(sStruct.rgptrssFixVal[0][2], "hellohello"); + EXPECT_EQ(sStruct.rgptrssFixVal[1][2], "ohhelloohhello"); + EXPECT_EQ(sStruct.rgptrssFixVal[0][3], "hallohallo"); + EXPECT_EQ(sStruct.rgptrssFixVal[1][3], "ohhalloohhallo"); + EXPECT_EQ(sStruct.rgptrssFixVal[0][4], "servusservus"); + EXPECT_EQ(sStruct.rgptrssFixVal[1][4], "ohservusohservus"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaStructSequenceArray) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.rgseq8Val[0].resize(100); + sStruct.rgseq8Val[1].resize(100); + for (size_t nIndex = 0; nIndex < sStruct.rgseq8Val[0].size(); nIndex++) + sStruct.rgseq8Val[0][nIndex] = static_cast(nIndex); + for (size_t nIndex = 0; nIndex < sStruct.rgseq8Val[1].size(); nIndex++) + sStruct.rgseq8Val[1][nIndex] = static_cast(nIndex); + sStruct.rgseqssVal[0].resize(3); + sStruct.rgseqssVal[1].resize(3); + sStruct.rgseqssVal[0][0] = "hello"; + sStruct.rgseqssVal[1][0] = "ohhello"; + sStruct.rgseqssVal[0][1] = "huhu"; + sStruct.rgseqssVal[1][1] = "ohhuhu"; + sStruct.rgseqssVal[0][2] = "hi"; + sStruct.rgseqssVal[1][2] = "ohhi"; + sStruct.rgseq8FixVal[0].resize(sStruct.rgseq8FixVal[0].capacity()); + sStruct.rgseq8FixVal[1].resize(sStruct.rgseq8FixVal[1].capacity()); + for (size_t nIndex = 0; nIndex < sStruct.rgseq8FixVal[0].size(); nIndex++) + sStruct.rgseq8FixVal[0][nIndex] = static_cast(nIndex); + for (size_t nIndex = 0; nIndex < sStruct.rgseq8FixVal[1].size(); nIndex++) + sStruct.rgseq8FixVal[1][nIndex] = static_cast(nIndex); + sStruct.rgseqssFixVal[0].resize(sStruct.seqssFixVal.capacity()); + sStruct.rgseqssFixVal[1].resize(sStruct.seqssFixVal.capacity()); + sStruct.rgseqssFixVal[0][0] = "hi"; + sStruct.rgseqssFixVal[1][0] = "ohhi"; + sStruct.rgseqssFixVal[0][1] = "hoi"; + sStruct.rgseqssFixVal[1][1] = "ohhoi"; + sStruct.rgseqssFixVal[0][2] = "hello"; + sStruct.rgseqssFixVal[1][2] = "ohhello"; + sStruct.rgseqssFixVal[0][3] = "hallo"; + sStruct.rgseqssFixVal[1][3] = "ohhallo"; + sStruct.rgseqssFixVal[0][4] = "servus"; + sStruct.rgseqssFixVal[1][4] = "ohservus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.rgseq8Val[0].size(), 200); + EXPECT_EQ(sStruct.rgseq8Val[1].size(), 300); + for (size_t nIndex = 0; nIndex < sStruct.rgseq8Val[0].size() / 2; nIndex++) + EXPECT_EQ(sStruct.rgseq8Val[0][nIndex], sStruct.rgseq8Val[0][nIndex + sStruct.rgseq8Val[0].size() / 2]); + for (size_t nIndex = 0; nIndex < sStruct.rgseq8Val[1].size() / 3; nIndex++) + EXPECT_EQ(sStruct.rgseq8Val[1][nIndex], sStruct.rgseq8Val[1][nIndex + sStruct.rgseq8Val[1].size() / 3]); + EXPECT_EQ(sStruct.rgseqssVal[0].size(), 6); + EXPECT_EQ(sStruct.rgseqssVal[1].size(), 9); + for (size_t nIndex = 0; nIndex < sStruct.rgseqssVal[0].size() / 2; nIndex++) + EXPECT_EQ(sStruct.rgseqssVal[0][nIndex], sStruct.rgseqssVal[0][nIndex + sStruct.rgseqssVal[0].size() / 2]); + for (size_t nIndex = 0; nIndex < sStruct.rgseqssVal[1].size() / 3; nIndex++) + EXPECT_EQ(sStruct.rgseqssVal[1][nIndex], sStruct.rgseqssVal[1][nIndex + sStruct.rgseqssVal[1].size() / 3]); + EXPECT_EQ(sStruct.rgseq8FixVal[0].size(), 20); + EXPECT_EQ(sStruct.rgseq8FixVal[1].size(), 20); + for (size_t nIndex = 0; nIndex < sStruct.rgseq8FixVal[0].size(); nIndex++) + EXPECT_EQ(sStruct.rgseq8FixVal[0][nIndex], static_cast(nIndex << 1)); + for (size_t nIndex = 0; nIndex < sStruct.rgseq8FixVal[1].size(); nIndex++) + EXPECT_EQ(sStruct.rgseq8FixVal[1][nIndex], static_cast(nIndex << 1)); + EXPECT_EQ(sStruct.rgseqssFixVal[0].size(), 5); + EXPECT_EQ(sStruct.rgseqssFixVal[1].size(), 5); + EXPECT_EQ(sStruct.rgseqssFixVal[0][0], "hihi"); + EXPECT_EQ(sStruct.rgseqssFixVal[1][0], "ohhiohhi"); + EXPECT_EQ(sStruct.rgseqssFixVal[0][1], "hoihoi"); + EXPECT_EQ(sStruct.rgseqssFixVal[1][1], "ohhoiohhoi"); + EXPECT_EQ(sStruct.rgseqssFixVal[0][2], "hellohello"); + EXPECT_EQ(sStruct.rgseqssFixVal[1][2], "ohhelloohhello"); + EXPECT_EQ(sStruct.rgseqssFixVal[0][3], "hallohallo"); + EXPECT_EQ(sStruct.rgseqssFixVal[1][3], "ohhalloohhallo"); + EXPECT_EQ(sStruct.rgseqssFixVal[0][4], "servusservus"); + EXPECT_EQ(sStruct.rgseqssFixVal[1][4], "ohservusohservus"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaStructInterfaceArray) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + struct SMultiply : public IMultiplyValue + { + size_t Multiply(size_t n) + { + return n * n; + } + } sMultiply; + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + + //sStruct.rgidVal[0] = sdv::GetInterfaceId(); + //sStruct.rgidVal[1] = sdv::GetInterfaceId(); + //sStruct.rgifcVal[0] = static_cast(&sMultiply); + //sStruct.rgifcVal[1] = static_cast(&sMultiply); + //sStruct.rgpMultiplyValue[0] = &sMultiply; + //sStruct.rgpMultiplyValue[1] = &sMultiply; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + //EXPECT_EQ(sStruct.rgidVal[0], sdv::GetInterfaceId()); + //EXPECT_EQ(sStruct.rgidVal[1], sdv::GetInterfaceId()); + //EXPECT_EQ(sStruct.rgifcVal[0].id(), sdv::GetInterfaceId()); + //EXPECT_EQ(sStruct.rgifcVal[1].id(), sdv::GetInterfaceId()); + //ASSERT_NE(sStruct.rgifcVal[0], nullptr); + //ASSERT_NE(sStruct.rgifcVal[1], nullptr); + //ASSERT_NE(sStruct.rgifcVal[0].get(), nullptr); + //ASSERT_NE(sStruct.rgifcVal[1].get(), nullptr); + //EXPECT_EQ(sStruct.rgifcVal[0].get()->Add(99), 19602); // 99 * 99 + 99 * 99 + //EXPECT_EQ(sStruct.rgifcVal[1].get()->Add(99), 19602); // 99 * 99 + 99 * 99 + //ASSERT_NE(sStruct.rgpMultiplyValue[0], nullptr); + //ASSERT_NE(sStruct.rgpMultiplyValue[1], nullptr); + //EXPECT_EQ(sStruct.rgpMultiplyValue[0]->Multiply(5), 625); // 5 * 5 * 5 * 5 + //EXPECT_EQ(sStruct.rgpMultiplyValue[1]->Multiply(5), 625); // 5 * 5 * 5 * 5 + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaStructSubCompoundArray) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.rgsSubVal[0].i = 1; + sStruct.rgsSubVal[1].i = 2; + // Incompatible serdes generation: BUG #398509 + //sStruct.rgsIndVal[0].i = 10; + //sStruct.rgsIndVal[1].i = 20; + // Unnamed struct with array is not yet supported due to incorret serdes code generation: BUG #398246 + //sStruct.rgsUnnamedVal[0].i = 100; + //sStruct.rgsUnnamedVal[1].i = 200; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.rgsSubVal[0].i, 2); + EXPECT_EQ(sStruct.rgsSubVal[1].i, 4); + //EXPECT_EQ(sStruct.rgsIndVal[0].i, 20); + //EXPECT_EQ(sStruct.rgsIndVal[1].i, 40); + //EXPECT_EQ(sStruct.rgsUnnamedVal[0].i, 120); + //EXPECT_EQ(sStruct.rgsUnnamedVal[1].i, 240); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaStructBooleanTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.tbVal = false; + EXPECT_FALSE(sStruct.tbVal); + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_TRUE(sStruct.tbVal); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaStructIntegralTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.tsVal = -10; + sStruct.tusVal = 10; + sStruct.tlVal = -20; + sStruct.tulVal = 20; + sStruct.tllVal = -30; + sStruct.tullVal = 30; + sStruct.ti8Val = -40; + sStruct.tui8Val = 40; + sStruct.ti16Val = -50; + sStruct.tui16Val = 50; + sStruct.ti32Val = -60; + sStruct.tui32Val = 60; + sStruct.ti64Val = -70; + sStruct.tui64Val = 70; + sStruct.tnVal = 80; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.tsVal, -11); + EXPECT_EQ(sStruct.tusVal, 11u); + EXPECT_EQ(sStruct.tlVal, -21); + EXPECT_EQ(sStruct.tulVal, 21u); + EXPECT_EQ(sStruct.tllVal, -31); + EXPECT_EQ(sStruct.tullVal, 31u); + EXPECT_EQ(static_cast(sStruct.ti8Val), static_cast(-41)); // Cast needed for ARM compatibility + EXPECT_EQ(sStruct.tui8Val, 41u); + EXPECT_EQ(sStruct.ti16Val, -51); + EXPECT_EQ(sStruct.tui16Val, 51u); + EXPECT_EQ(sStruct.ti32Val, -61); + EXPECT_EQ(sStruct.tui32Val, 61u); + EXPECT_EQ(sStruct.ti64Val, -71); + EXPECT_EQ(sStruct.tui64Val, 71u); + EXPECT_EQ(sStruct.tnVal, 81); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaStructCharacterTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.tcVal = 'A'; + sStruct.tc16Val = u'D'; + sStruct.tc32Val = U'G'; + sStruct.twcVal = L'J'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.tcVal, 'B'); + EXPECT_EQ(sStruct.tc16Val, u'E'); + EXPECT_EQ(sStruct.tc32Val, U'H'); + EXPECT_EQ(sStruct.twcVal, L'K'); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaStructFloatingPointTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + float fVal = -1234.5678f; + sStruct.tfVal = fVal; + double dVal = 8765.4321; + sStruct.tdVal = dVal; + long double ldVal = -1234.4321; + sStruct.tldVal = ldVal; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.tfVal, fVal - 1000.000f); + EXPECT_EQ(sStruct.tdVal, dVal + 1000.000); + EXPECT_EQ(sStruct.tldVal, ldVal - 1000.000); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaStructEnumTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.teHelloVal = EHello::hello; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.teHelloVal, EHello::hallo); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaStructStringTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.tssVal = "hi"; + sStruct.tss8Val = "huhu"; + sStruct.tss16Val = u"hey"; + sStruct.tss32Val = U"hello"; + sStruct.twssVal = L"servus"; + sStruct.tssFixVal = "hi"; + sStruct.tss8FixVal = "huhu"; + sStruct.tss16FixVal = u"hey"; + sStruct.tss32FixVal = U"hello"; + sStruct.twssFixVal = L"servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.tssVal, "hij"); + EXPECT_EQ(sStruct.tss8Val, "huhuv"); + EXPECT_EQ(sStruct.tss16Val, u"heyz"); + EXPECT_EQ(sStruct.tss32Val, U"hellop"); + EXPECT_EQ(sStruct.twssVal, L"servust"); + EXPECT_EQ(sStruct.tssFixVal, "hij"); + EXPECT_EQ(sStruct.tss8FixVal, "huhuv"); + EXPECT_EQ(sStruct.tss16FixVal, u"heyz"); + EXPECT_EQ(sStruct.tss32FixVal, U"hellop"); + EXPECT_EQ(sStruct.twssFixVal, L"servust"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +// Using typedefs of template based types produced an error: BUG #398266 +TEST(IPC_Communication_Test_UDS, DISABLED_MarshallMegaStructPointerTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + //sStruct.tptr8Val.resize(100); + //for (size_t nIndex = 0; nIndex < sStruct.tptr8Val.size(); nIndex++) + // sStruct.tptr8Val[nIndex] = static_cast(nIndex); + //sStruct.tptrssVal.resize(3); + //sStruct.tptrssVal[0] = "hello"; + //sStruct.tptrssVal[1] = "huhu"; + //sStruct.tptrssVal[2] = "hi"; + //sStruct.tptr8FixVal.resize(sStruct.tptr8FixVal.capacity()); + //for (size_t nIndex = 0; nIndex < sStruct.tptr8FixVal.size(); nIndex++) + // sStruct.tptr8FixVal[nIndex] = static_cast(nIndex); + //sStruct.tptrssFixVal.resize(sStruct.tptrssFixVal.capacity()); + //sStruct.tptrssFixVal[0] = "hi"; + //sStruct.tptrssFixVal[1] = "hoi"; + //sStruct.tptrssFixVal[2] = "hello"; + //sStruct.tptrssFixVal[3] = "hallo"; + //sStruct.tptrssFixVal[4] = "servus"; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + //EXPECT_EQ(sStruct.tptr8Val.size(), 200); + //for (size_t nIndex = 0; nIndex < sStruct.tptr8Val.size() / 2; nIndex++) + // EXPECT_EQ(sStruct.tptr8Val[nIndex], sStruct.tptr8Val[nIndex + sStruct.tptr8Val.size() / 2]); + //EXPECT_EQ(sStruct.tptrssVal.size(), 6); + //for (size_t nIndex = 0; nIndex < sStruct.tptrssVal.size() / 2; nIndex++) + // EXPECT_EQ(sStruct.tptrssVal[nIndex], sStruct.tptrssVal[nIndex + sStruct.tptrssVal.size() / 2]); + //EXPECT_EQ(sStruct.tptr8FixVal.size(), 20); + //for (size_t nIndex = 0; nIndex < sStruct.tptr8FixVal.size(); nIndex++) + // EXPECT_EQ(sStruct.tptr8FixVal[nIndex], static_cast(nIndex << 1)); + //EXPECT_EQ(sStruct.tptrssFixVal.size(), 5); + //EXPECT_EQ(sStruct.tptrssFixVal[0], "hihi"); + //EXPECT_EQ(sStruct.tptrssFixVal[1], "hoihoi"); + //EXPECT_EQ(sStruct.tptrssFixVal[2], "hellohello"); + //EXPECT_EQ(sStruct.tptrssFixVal[3], "hallohallo"); + //EXPECT_EQ(sStruct.tptrssFixVal[4], "servusservus"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +// Using typedefs of template based types produced an error: BUG #398266 +TEST(IPC_Communication_Test_UDS, DISABLED_MarshallMegaStructSequenceTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + //sStruct.tseq8Val.resize(100); + //for (size_t nIndex = 0; nIndex < sStruct.tseq8Val.size(); nIndex++) + // sStruct.tseq8Val[nIndex] = static_cast(nIndex); + //sStruct.tseqssVal.resize(3); + //sStruct.tseqssVal[0] = "hello"; + //sStruct.tseqssVal[1] = "huhu"; + //sStruct.tseqssVal[2] = "hi"; + //sStruct.tseq8FixVal.resize(sStruct.tseq8FixVal.capacity()); + //for (size_t nIndex = 0; nIndex < sStruct.tseq8FixVal.size(); nIndex++) + // sStruct.tseq8FixVal[nIndex] = static_cast(nIndex); + //sStruct.tseqssFixVal.resize(sStruct.tseqssFixVal.capacity()); + //sStruct.tseqssFixVal[0] = "hi"; + //sStruct.tseqssFixVal[1] = "hoi"; + //sStruct.tseqssFixVal[2] = "hello"; + //sStruct.tseqssFixVal[3] = "hallo"; + //sStruct.tseqssFixVal[4] = "servus"; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + //EXPECT_EQ(sStruct.tseq8Val.size(), 200); + //for (size_t nIndex = 0; nIndex < sStruct.tseq8Val.size() / 2; nIndex++) + // EXPECT_EQ(sStruct.tseq8Val[nIndex], sStruct.tseq8Val[nIndex + sStruct.tseq8Val.size() / 2]); + //EXPECT_EQ(sStruct.tseqssVal.size(), 6); + //for (size_t nIndex = 0; nIndex < sStruct.tseqssVal.size() / 2; nIndex++) + // EXPECT_EQ(sStruct.tseqssVal[nIndex], sStruct.tseqssVal[nIndex + sStruct.tseqssVal.size() / 2]); + //EXPECT_EQ(sStruct.tseq8FixVal.size(), 20); + //for (size_t nIndex = 0; nIndex < sStruct.tseq8FixVal.size(); nIndex++) + // EXPECT_EQ(sStruct.tseq8FixVal[nIndex], static_cast(nIndex << 1)); + //EXPECT_EQ(sStruct.tseqssFixVal.size(), 5); + //EXPECT_EQ(sStruct.tseqssFixVal[0], "hihi"); + //EXPECT_EQ(sStruct.tseqssFixVal[1], "hoihoi"); + //EXPECT_EQ(sStruct.tseqssFixVal[2], "hellohello"); + //EXPECT_EQ(sStruct.tseqssFixVal[3], "hallohallo"); + //EXPECT_EQ(sStruct.tseqssFixVal[4], "servusservus"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaStructInterfaceTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + struct SMultiply : public IMultiplyValue + { + size_t Multiply(size_t n) + { + return n * n; + } + } sMultiply; + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + + sStruct.tidVal = sdv::GetInterfaceId(); + sStruct.tifcVal = static_cast(&sMultiply); + // Typedef of interfaces current not possible. BUG #399464 + //sStruct.ptMultiplyValue = &sMultiply; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.tidVal, sdv::GetInterfaceId()); + EXPECT_EQ(sStruct.tifcVal.id(), sdv::GetInterfaceId()); + ASSERT_NE(sStruct.tifcVal, nullptr); + ASSERT_NE(sStruct.tifcVal.get(), nullptr); + EXPECT_EQ(sStruct.tifcVal.get()->Add(99), 19602); // 99 * 99 + 99 * 99 + // Typedef of interfaces current not possible. BUG #399464 + //ASSERT_NE(sStruct.tpMultiplyValue, nullptr); + //EXPECT_EQ(sStruct.tpMultiplyValue->Multiply(5), 625); // 5 * 5 * 5 * 5 + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaStructSubCompoundTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.tsSubVal.i = 1; + sStruct.tsIndVal.i = 10; + // Incompatible serdes generation: BUG #398509 + //sStruct.tsUnnamedVal.i = 100; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.tsSubVal.i, 2); + EXPECT_EQ(sStruct.tsIndVal.i, 20); + // Incompatible serdes generation: BUG #398509 + //EXPECT_EQ(sStruct.tsUnnamedVal.i, 120); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaTypeBasedUnionBoolean) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(1); + uUnion.bVal = false; + EXPECT_FALSE(uUnion.bVal); + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_TRUE(uUnion.bVal); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaTypeBasedUnionIntegral) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(2); + uUnion.sVal = -10; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.sVal, -11); + uUnion.switch_to(3); + uUnion.usVal = 10; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.usVal, 11u); + uUnion.switch_to(4); + uUnion.lVal = -20; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.lVal, -21); + uUnion.switch_to(5); + uUnion.ulVal = 20; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ulVal, 21u); + uUnion.switch_to(6); + uUnion.llVal = -30; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.llVal, -31); + uUnion.switch_to(7); + uUnion.ullVal = 30; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ullVal, 31u); + uUnion.switch_to(8); + uUnion.i8Val = -40; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(static_cast(uUnion.i8Val), static_cast(-41)); // Cast needed for ARM compatibility + uUnion.switch_to(9); + uUnion.ui8Val = 40; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ui8Val, 41u); + uUnion.switch_to(10); + uUnion.i16Val = -50; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.i16Val, -51); + uUnion.switch_to(11); + uUnion.ui16Val = 50; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ui16Val, 51u); + uUnion.switch_to(12); + uUnion.i32Val = -60; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.i32Val, -61); + uUnion.switch_to(13); + uUnion.ui32Val = 60; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ui32Val, 61u); + uUnion.switch_to(14); + uUnion.i64Val = -70; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.i64Val, -71); + uUnion.switch_to(15); + uUnion.ui64Val = 70; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ui64Val, 71u); + uUnion.switch_to(23); + uUnion.nVal = 80; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.nVal, 81); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaTypeBasedUnionCharacter) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(16); + uUnion.cVal = 'A'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.cVal, 'B'); + uUnion.switch_to(17); + uUnion.c16Val = u'D'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.c16Val, u'E'); + uUnion.switch_to(18); + uUnion.c32Val = U'G'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.c32Val, U'H'); + uUnion.switch_to(19); + uUnion.wcVal = L'J'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.wcVal, L'K'); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaTypeBasedUnionFloatingPoint) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(20); + float fVal = -1234.5678f; + uUnion.fVal = fVal; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.fVal, fVal - 1000.000f); + uUnion.switch_to(21); + double dVal = 8765.4321; + uUnion.dVal = dVal; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.dVal, dVal + 1000.000); + // GCC issue with generated initialization in the consructor for the "long double" type: BUG #3982727 + //uUnion.switch_to(22); + //long double ldVal = -1234.4321; + //uUnion.ldVal = ldVal; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + //EXPECT_EQ(uUnion.ldVal, ldVal - 1000.000); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaTypeBasedUnionEnum) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(24); + uUnion.eHelloVal = EHello::hello; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.eHelloVal, EHello::hallo); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaTypeBasedUnionString) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(26); + uUnion.ssVal = "hi"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ssVal, "hij"); + uUnion.switch_to(27); + uUnion.ss8Val = "huhu"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ss8Val, "huhuv"); + uUnion.switch_to(28); + uUnion.ss16Val = u"hey"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ss16Val, u"heyz"); + uUnion.switch_to(29); + uUnion.ss32Val = U"hello"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ss32Val, U"hellop"); + uUnion.switch_to(30); + uUnion.wssVal = L"servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.wssVal, L"servust"); + uUnion.switch_to(31); + uUnion.ssFixVal = "hi"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ssFixVal, "hij"); + uUnion.switch_to(32); + uUnion.ss8FixVal = "huhu"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ss8FixVal, "huhuv"); + uUnion.switch_to(33); + uUnion.ss16FixVal = u"hey"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ss16FixVal, u"heyz"); + uUnion.switch_to(34); + uUnion.ss32FixVal = U"hello"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ss32FixVal, U"hellop"); + uUnion.switch_to(35); + uUnion.wssFixVal = L"servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.wssFixVal, L"servust"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaTypeBasedUnionPointer) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(36); + uUnion.ptr8Val.resize(100); + for (size_t nIndex = 0; nIndex < uUnion.ptr8Val.size(); nIndex++) + uUnion.ptr8Val[nIndex] = static_cast(nIndex); + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ptr8Val.size(), 200); + for (size_t nIndex = 0; nIndex < uUnion.ptr8Val.size() / 2; nIndex++) + EXPECT_EQ(uUnion.ptr8Val[nIndex], uUnion.ptr8Val[nIndex + uUnion.ptr8Val.size() / 2]); + uUnion.switch_to(37); + uUnion.ptrssVal.resize(3); + uUnion.ptrssVal[0] = "hello"; + uUnion.ptrssVal[1] = "huhu"; + uUnion.ptrssVal[2] = "hi"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ptrssVal.size(), 6); + for (size_t nIndex = 0; nIndex < uUnion.ptrssVal.size() / 2; nIndex++) + EXPECT_EQ(uUnion.ptrssVal[nIndex], uUnion.ptrssVal[nIndex + uUnion.ptrssVal.size() / 2]); + uUnion.switch_to(38); + uUnion.ptr8FixVal.resize(uUnion.ptr8FixVal.capacity()); + for (size_t nIndex = 0; nIndex < uUnion.ptr8FixVal.size(); nIndex++) + uUnion.ptr8FixVal[nIndex] = static_cast(nIndex); + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ptr8FixVal.size(), 20); + for (size_t nIndex = 0; nIndex < uUnion.ptr8FixVal.size(); nIndex++) + EXPECT_EQ(uUnion.ptr8FixVal[nIndex], static_cast(nIndex << 1)); + uUnion.switch_to(39); + uUnion.ptrssFixVal.resize(uUnion.ptrssFixVal.capacity()); + uUnion.ptrssFixVal[0] = "hi"; + uUnion.ptrssFixVal[1] = "hoi"; + uUnion.ptrssFixVal[2] = "hello"; + uUnion.ptrssFixVal[3] = "hallo"; + uUnion.ptrssFixVal[4] = "servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ptrssFixVal.size(), 5); + EXPECT_EQ(uUnion.ptrssFixVal[0], "hihi"); + EXPECT_EQ(uUnion.ptrssFixVal[1], "hoihoi"); + EXPECT_EQ(uUnion.ptrssFixVal[2], "hellohello"); + EXPECT_EQ(uUnion.ptrssFixVal[3], "hallohallo"); + EXPECT_EQ(uUnion.ptrssFixVal[4], "servusservus"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaTypeBasedUnionSequence) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(36); + uUnion.seq8Val.resize(100); + for (size_t nIndex = 0; nIndex < uUnion.seq8Val.size(); nIndex++) + uUnion.seq8Val[nIndex] = static_cast(nIndex); + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.seq8Val.size(), 200); + for (size_t nIndex = 0; nIndex < uUnion.seq8Val.size() / 2; nIndex++) + EXPECT_EQ(uUnion.seq8Val[nIndex], uUnion.seq8Val[nIndex + uUnion.seq8Val.size() / 2]); + uUnion.switch_to(37); + uUnion.seqssVal.resize(3); + uUnion.seqssVal[0] = "hello"; + uUnion.seqssVal[1] = "huhu"; + uUnion.seqssVal[2] = "hi"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.seqssVal.size(), 6); + for (size_t nIndex = 0; nIndex < uUnion.seqssVal.size() / 2; nIndex++) + EXPECT_EQ(uUnion.seqssVal[nIndex], uUnion.seqssVal[nIndex + uUnion.seqssVal.size() / 2]); + uUnion.switch_to(38); + uUnion.seq8FixVal.resize(uUnion.seq8FixVal.capacity()); + for (size_t nIndex = 0; nIndex < uUnion.seq8FixVal.size(); nIndex++) + uUnion.seq8FixVal[nIndex] = static_cast(nIndex); + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.seq8FixVal.size(), 20); + for (size_t nIndex = 0; nIndex < uUnion.seq8FixVal.size(); nIndex++) + EXPECT_EQ(uUnion.seq8FixVal[nIndex], static_cast(nIndex << 1)); + uUnion.switch_to(39); + uUnion.seqssFixVal.resize(uUnion.seqssFixVal.capacity()); + uUnion.seqssFixVal[0] = "hi"; + uUnion.seqssFixVal[1] = "hoi"; + uUnion.seqssFixVal[2] = "hello"; + uUnion.seqssFixVal[3] = "hallo"; + uUnion.seqssFixVal[4] = "servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.seqssFixVal.size(), 5); + EXPECT_EQ(uUnion.seqssFixVal[0], "hihi"); + EXPECT_EQ(uUnion.seqssFixVal[1], "hoihoi"); + EXPECT_EQ(uUnion.seqssFixVal[2], "hellohello"); + EXPECT_EQ(uUnion.seqssFixVal[3], "hallohallo"); + EXPECT_EQ(uUnion.seqssFixVal[4], "servusservus"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaTypeBasedUnionInterface) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + struct SMultiply : public IMultiplyValue + { + size_t Multiply(size_t n) + { + return n * n; + } + } sMultiply; + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(25); + + uUnion.idVal = sdv::GetInterfaceId(); + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.idVal, sdv::GetInterfaceId()); + uUnion.switch_to(44); + uUnion.ifcVal = static_cast(&sMultiply); + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ifcVal.id(), sdv::GetInterfaceId()); + ASSERT_NE(uUnion.ifcVal, nullptr); + ASSERT_NE(uUnion.ifcVal.get(), nullptr); + EXPECT_EQ(uUnion.ifcVal.get()->Add(99), 19602); // 99 * 99 + 99 * 99 + uUnion.switch_to(45); + uUnion.pMultiplyValue = &sMultiply; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + ASSERT_NE(uUnion.pMultiplyValue, nullptr); + EXPECT_EQ(uUnion.pMultiplyValue->Multiply(5), 625); // 5 * 5 * 5 * 5 + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaTypeBasedUnionSubCompound) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(46); + uUnion.sSubVal.i = 1; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.sSubVal.i, 2); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaTypeBasedUnionBooleanTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(201); + uUnion.tbVal = false; + EXPECT_FALSE(uUnion.tbVal); + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_TRUE(uUnion.tbVal); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaTypeBasedUnionIntegralTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(202); + uUnion.tsVal = -10; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tsVal, -11); + uUnion.switch_to(203); + uUnion.tusVal = 10; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tusVal, 11u); + uUnion.switch_to(204); + uUnion.tlVal = -20; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tlVal, -21); + uUnion.switch_to(205); + uUnion.tulVal = 20; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tulVal, 21u); + uUnion.switch_to(206); + uUnion.tllVal = -30; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tllVal, -31); + uUnion.switch_to(207); + uUnion.tullVal = 30; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tullVal, 31u); + uUnion.switch_to(208); + uUnion.ti8Val = -40; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(static_cast(uUnion.ti8Val), static_cast(-41)); // Cast needed for ARM compatibility + uUnion.switch_to(209); + uUnion.tui8Val = 40; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tui8Val, 41u); + uUnion.switch_to(210); + uUnion.ti16Val = -50; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ti16Val, -51); + uUnion.switch_to(211); + uUnion.tui16Val = 50; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tui16Val, 51u); + uUnion.switch_to(212); + uUnion.ti32Val = -60; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ti32Val, -61); + uUnion.switch_to(213); + uUnion.tui32Val = 60; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tui32Val, 61u); + uUnion.switch_to(214); + uUnion.ti64Val = -70; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ti64Val, -71); + uUnion.switch_to(215); + uUnion.tui64Val = 70; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tui64Val, 71u); + uUnion.switch_to(223); + uUnion.tnVal = 80; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tnVal, 81); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaTypeBasedUnionCharacterTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(216); + uUnion.tcVal = 'A'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tcVal, 'B'); + uUnion.switch_to(217); + uUnion.tc16Val = u'D'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tc16Val, u'E'); + uUnion.switch_to(218); + uUnion.tc32Val = U'G'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tc32Val, U'H'); + uUnion.switch_to(219); + uUnion.twcVal = L'J'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.twcVal, L'K'); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaTypeBasedUnionFloatingPointTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(220); + float fVal = -1234.5678f; + uUnion.tfVal = fVal; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tfVal, fVal - 1000.000f); + uUnion.switch_to(221); + double dVal = 8765.4321; + uUnion.tdVal = dVal; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tdVal, dVal + 1000.000); + // GCC issue with generated initialization in the consructor for the "long double" type: BUG #3982727 + //uUnion.switch_to(222); + //long double ldVal = -1234.4321; + //uUnion.tldVal = ldVal; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + //EXPECT_EQ(uUnion.tldVal, ldVal - 1000.000); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaTypeBasedUnionEnumTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(224); + uUnion.teHelloVal = EHello::hello; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.teHelloVal, EHello::hallo); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaTypeBasedUnionStringTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(226); + uUnion.tssVal = "hi"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tssVal, "hij"); + uUnion.switch_to(227); + uUnion.tss8Val = "huhu"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tss8Val, "huhuv"); + uUnion.switch_to(228); + uUnion.tss16Val = u"hey"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tss16Val, u"heyz"); + uUnion.switch_to(229); + uUnion.tss32Val = U"hello"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tss32Val, U"hellop"); + uUnion.switch_to(230); + uUnion.twssVal = L"servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.twssVal, L"servust"); + uUnion.switch_to(231); + uUnion.tssFixVal = "hi"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tssFixVal, "hij"); + uUnion.switch_to(232); + uUnion.tss8FixVal = "huhu"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tss8FixVal, "huhuv"); + uUnion.switch_to(233); + uUnion.tss16FixVal = u"hey"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tss16FixVal, u"heyz"); + uUnion.switch_to(234); + uUnion.tss32FixVal = U"hello"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tss32FixVal, U"hellop"); + uUnion.switch_to(235); + uUnion.twssFixVal = L"servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.twssFixVal, L"servust"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +// Using typedefs of template based types produced an error: BUG #398266 +TEST(IPC_Communication_Test_UDS, DISABLED_MarshallMegaTypeBasedUnionPointerTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(236); + //uUnion.tptr8Val.resize(100); + //for (size_t nIndex = 0; nIndex < uUnion.tptr8Val.size(); nIndex++) + // uUnion.tptr8Val[nIndex] = static_cast(nIndex); + //EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + //EXPECT_EQ(uUnion.tptr8Val.size(), 200); + //for (size_t nIndex = 0; nIndex < uUnion.tptr8Val.size() / 2; nIndex++) + // EXPECT_EQ(uUnion.tptr8Val[nIndex], uUnion.tptr8Val[nIndex + uUnion.tptr8Val.size() / 2]); + //uUnion.switch_to(237); + //uUnion.tptrssVal.resize(3); + //uUnion.tptrssVal[0] = "hello"; + //uUnion.tptrssVal[1] = "huhu"; + //uUnion.tptrssVal[2] = "hi"; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + //EXPECT_EQ(uUnion.tptrssVal.size(), 6); + //for (size_t nIndex = 0; nIndex < uUnion.tptrssVal.size() / 2; nIndex++) + // EXPECT_EQ(uUnion.tptrssVal[nIndex], uUnion.tptrssVal[nIndex + uUnion.tptrssVal.size() / 2]); + //uUnion.switch_to(238); + //uUnion.tptr8FixVal.resize(uUnion.tptr8FixVal.capacity()); + //for (size_t nIndex = 0; nIndex < uUnion.tptr8FixVal.size(); nIndex++) + // uUnion.tptr8FixVal[nIndex] = static_cast(nIndex); + //EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + //EXPECT_EQ(uUnion.tptr8FixVal.size(), 20); + //for (size_t nIndex = 0; nIndex < uUnion.tptr8FixVal.size(); nIndex++) + // EXPECT_EQ(uUnion.tptr8FixVal[nIndex], static_cast(nIndex << 1)); + //uUnion.switch_to(239); + //uUnion.tptrssFixVal.resize(uUnion.tptrssFixVal.capacity()); + //uUnion.tptrssFixVal[0] = "hi"; + //uUnion.tptrssFixVal[1] = "hoi"; + //uUnion.tptrssFixVal[2] = "hello"; + //uUnion.tptrssFixVal[3] = "hallo"; + //uUnion.tptrssFixVal[4] = "servus"; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + //EXPECT_EQ(uUnion.tptrssFixVal.size(), 5); + //EXPECT_EQ(uUnion.tptrssFixVal[0], "hihi"); + //EXPECT_EQ(uUnion.tptrssFixVal[1], "hoihoi"); + //EXPECT_EQ(uUnion.tptrssFixVal[2], "hellohello"); + //EXPECT_EQ(uUnion.tptrssFixVal[3], "hallohallo"); + //EXPECT_EQ(uUnion.tptrssFixVal[4], "servusservus"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +// Using typedefs of template based types produced an error: BUG #398266 +TEST(IPC_Communication_Test_UDS, DISABLED_MarshallMegaTypeBasedUnionSequenceTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(236); + //uUnion.tseq8Val.resize(100); + //for (size_t nIndex = 0; nIndex < uUnion.tseq8Val.size(); nIndex++) + // uUnion.tseq8Val[nIndex] = static_cast(nIndex); + //EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + //EXPECT_EQ(uUnion.tseq8Val.size(), 200); + //for (size_t nIndex = 0; nIndex < uUnion.tseq8Val.size() / 2; nIndex++) + // EXPECT_EQ(uUnion.tseq8Val[nIndex], uUnion.tseq8Val[nIndex + uUnion.tseq8Val.size() / 2]); + //uUnion.switch_to(237); + //uUnion.tseqssVal.resize(3); + //uUnion.tseqssVal[0] = "hello"; + //uUnion.tseqssVal[1] = "huhu"; + //uUnion.tseqssVal[2] = "hi"; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + //EXPECT_EQ(uUnion.tseqssVal.size(), 6); + //for (size_t nIndex = 0; nIndex < uUnion.tseqssVal.size() / 2; nIndex++) + // EXPECT_EQ(uUnion.tseqssVal[nIndex], uUnion.tseqssVal[nIndex + uUnion.tseqssVal.size() / 2]); + //uUnion.switch_to(238); + //uUnion.tseq8FixVal.resize(uUnion.tseq8FixVal.capacity()); + //for (size_t nIndex = 0; nIndex < uUnion.tseq8FixVal.size(); nIndex++) + // uUnion.tseq8FixVal[nIndex] = static_cast(nIndex); + //EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + //EXPECT_EQ(uUnion.tseq8FixVal.size(), 20); + //for (size_t nIndex = 0; nIndex < uUnion.tseq8FixVal.size(); nIndex++) + // EXPECT_EQ(uUnion.tseq8FixVal[nIndex], static_cast(nIndex << 1)); + //uUnion.switch_to(239); + //uUnion.tseqssFixVal.resize(uUnion.tseqssFixVal.capacity()); + //uUnion.tseqssFixVal[0] = "hi"; + //uUnion.tseqssFixVal[1] = "hoi"; + //uUnion.tseqssFixVal[2] = "hello"; + //uUnion.tseqssFixVal[3] = "hallo"; + //uUnion.tseqssFixVal[4] = "servus"; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + //EXPECT_EQ(uUnion.tseqssFixVal.size(), 5); + //EXPECT_EQ(uUnion.tseqssFixVal[0], "hihi"); + //EXPECT_EQ(uUnion.tseqssFixVal[1], "hoihoi"); + //EXPECT_EQ(uUnion.tseqssFixVal[2], "hellohello"); + //EXPECT_EQ(uUnion.tseqssFixVal[3], "hallohallo"); + //EXPECT_EQ(uUnion.tseqssFixVal[4], "servusservus"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaTypeBasedUnionInterfaceTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + struct SMultiply : public IMultiplyValue + { + size_t Multiply(size_t n) + { + return n * n; + } + } sMultiply; + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(225); + + uUnion.tidVal = sdv::GetInterfaceId(); + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tidVal, sdv::GetInterfaceId()); + uUnion.switch_to(244); + uUnion.tifcVal = static_cast(&sMultiply); + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tifcVal.id(), sdv::GetInterfaceId()); + ASSERT_NE(uUnion.tifcVal, nullptr); + ASSERT_NE(uUnion.tifcVal.get(), nullptr); + EXPECT_EQ(uUnion.tifcVal.get()->Add(99), 19602); // 99 * 99 + 99 * 99 + // Typedef of interfaces current not possible. BUG #399464 + //uUnion.switch_to(245); + //uUnion.tpMultiplyValue = &sMultiply; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + //ASSERT_NE(uUnion.tpMultiplyValue, nullptr); + //EXPECT_EQ(uUnion.tpMultiplyValue->Multiply(5), 625); // 5 * 5 * 5 * 5 + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaTypeBasedUnionSubCompoundTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(246); + uUnion.tsSubVal.i = 1; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tsSubVal.i, 2); + uUnion.switch_to(247); + uUnion.tsIndVal.i = 10; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tsIndVal.i, 20); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaVarBasedUnionBoolean) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(1); + sUnion.uVal.bVal = false; + EXPECT_FALSE(sUnion.uVal.bVal); + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_TRUE(sUnion.uVal.bVal); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaVarBasedUnionIntegral) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(2); + sUnion.uVal.sVal = -10; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.sVal, -11); + sUnion.switch_to(3); + sUnion.uVal.usVal = 10; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.usVal, 11u); + sUnion.switch_to(4); + sUnion.uVal.lVal = -20; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.lVal, -21); + sUnion.switch_to(5); + sUnion.uVal.ulVal = 20; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ulVal, 21u); + sUnion.switch_to(6); + sUnion.uVal.llVal = -30; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.llVal, -31); + sUnion.switch_to(7); + sUnion.uVal.ullVal = 30; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ullVal, 31u); + sUnion.switch_to(8); + sUnion.uVal.i8Val = -40; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(static_cast(sUnion.uVal.i8Val), static_cast(-41)); // Cast needed for ARM compatibility + sUnion.switch_to(9); + sUnion.uVal.ui8Val = 40; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ui8Val, 41u); + sUnion.switch_to(10); + sUnion.uVal.i16Val = -50; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.i16Val, -51); + sUnion.switch_to(11); + sUnion.uVal.ui16Val = 50; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ui16Val, 51u); + sUnion.switch_to(12); + sUnion.uVal.i32Val = -60; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.i32Val, -61); + sUnion.switch_to(13); + sUnion.uVal.ui32Val = 60; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ui32Val, 61u); + sUnion.switch_to(14); + sUnion.uVal.i64Val = -70; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.i64Val, -71); + sUnion.switch_to(15); + sUnion.uVal.ui64Val = 70; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ui64Val, 71u); + sUnion.switch_to(23); + sUnion.uVal.nVal = 80; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.nVal, 81); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaVarBasedUnionCharacter) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(16); + sUnion.uVal.cVal = 'A'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.cVal, 'B'); + sUnion.switch_to(17); + sUnion.uVal.c16Val = u'D'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.c16Val, u'E'); + sUnion.switch_to(18); + sUnion.uVal.c32Val = U'G'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.c32Val, U'H'); + sUnion.switch_to(19); + sUnion.uVal.wcVal = L'J'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.wcVal, L'K'); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaVarBasedUnionFloatingPoint) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(20); + float fVal = -1234.5678f; + sUnion.uVal.fVal = fVal; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.fVal, fVal - 1000.000f); + sUnion.switch_to(21); + double dVal = 8765.4321; + sUnion.uVal.dVal = dVal; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.dVal, dVal + 1000.000); + // GCC issue with generated initialization in the consructor for the "long double" type: BUG #3982727 + //sUnion.switch_to(22); + //long double ldVal = -1234.4321; + //sUnion.uVal.ldVal = ldVal; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + //EXPECT_EQ(sUnion.uVal.ldVal, ldVal - 1000.000); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaVarBasedUnionEnum) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(24); + sUnion.uVal.eHelloVal = EHello::hello; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.eHelloVal, EHello::hallo); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaVarBasedUnionString) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(26); + sUnion.uVal.ssVal = "hi"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ssVal, "hij"); + sUnion.switch_to(27); + sUnion.uVal.ss8Val = "huhu"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ss8Val, "huhuv"); + sUnion.switch_to(28); + sUnion.uVal.ss16Val = u"hey"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ss16Val, u"heyz"); + sUnion.switch_to(29); + sUnion.uVal.ss32Val = U"hello"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ss32Val, U"hellop"); + sUnion.switch_to(30); + sUnion.uVal.wssVal = L"servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.wssVal, L"servust"); + sUnion.switch_to(31); + sUnion.uVal.ssFixVal = "hi"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ssFixVal, "hij"); + sUnion.switch_to(32); + sUnion.uVal.ss8FixVal = "huhu"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ss8FixVal, "huhuv"); + sUnion.switch_to(33); + sUnion.uVal.ss16FixVal = u"hey"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ss16FixVal, u"heyz"); + sUnion.switch_to(34); + sUnion.uVal.ss32FixVal = U"hello"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ss32FixVal, U"hellop"); + sUnion.switch_to(35); + sUnion.uVal.wssFixVal = L"servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.wssFixVal, L"servust"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaVarBasedUnionPointer) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(36); + sUnion.uVal.ptr8Val.resize(100); + for (size_t nIndex = 0; nIndex < sUnion.uVal.ptr8Val.size(); nIndex++) + sUnion.uVal.ptr8Val[nIndex] = static_cast(nIndex); + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ptr8Val.size(), 200); + for (size_t nIndex = 0; nIndex < sUnion.uVal.ptr8Val.size() / 2; nIndex++) + EXPECT_EQ(sUnion.uVal.ptr8Val[nIndex], sUnion.uVal.ptr8Val[nIndex + sUnion.uVal.ptr8Val.size() / 2]); + sUnion.switch_to(37); + sUnion.uVal.ptrssVal.resize(3); + sUnion.uVal.ptrssVal[0] = "hello"; + sUnion.uVal.ptrssVal[1] = "huhu"; + sUnion.uVal.ptrssVal[2] = "hi"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ptrssVal.size(), 6); + for (size_t nIndex = 0; nIndex < sUnion.uVal.ptrssVal.size() / 2; nIndex++) + EXPECT_EQ(sUnion.uVal.ptrssVal[nIndex], sUnion.uVal.ptrssVal[nIndex + sUnion.uVal.ptrssVal.size() / 2]); + sUnion.switch_to(38); + sUnion.uVal.ptr8FixVal.resize(sUnion.uVal.ptr8FixVal.capacity()); + for (size_t nIndex = 0; nIndex < sUnion.uVal.ptr8FixVal.size(); nIndex++) + sUnion.uVal.ptr8FixVal[nIndex] = static_cast(nIndex); + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ptr8FixVal.size(), 20); + for (size_t nIndex = 0; nIndex < sUnion.uVal.ptr8FixVal.size(); nIndex++) + EXPECT_EQ(sUnion.uVal.ptr8FixVal[nIndex], static_cast(nIndex << 1)); + sUnion.switch_to(39); + sUnion.uVal.ptrssFixVal.resize(sUnion.uVal.ptrssFixVal.capacity()); + sUnion.uVal.ptrssFixVal[0] = "hi"; + sUnion.uVal.ptrssFixVal[1] = "hoi"; + sUnion.uVal.ptrssFixVal[2] = "hello"; + sUnion.uVal.ptrssFixVal[3] = "hallo"; + sUnion.uVal.ptrssFixVal[4] = "servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ptrssFixVal.size(), 5); + EXPECT_EQ(sUnion.uVal.ptrssFixVal[0], "hihi"); + EXPECT_EQ(sUnion.uVal.ptrssFixVal[1], "hoihoi"); + EXPECT_EQ(sUnion.uVal.ptrssFixVal[2], "hellohello"); + EXPECT_EQ(sUnion.uVal.ptrssFixVal[3], "hallohallo"); + EXPECT_EQ(sUnion.uVal.ptrssFixVal[4], "servusservus"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaVarBasedUnionSequence) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(36); + sUnion.uVal.seq8Val.resize(100); + for (size_t nIndex = 0; nIndex < sUnion.uVal.seq8Val.size(); nIndex++) + sUnion.uVal.seq8Val[nIndex] = static_cast(nIndex); + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.seq8Val.size(), 200); + for (size_t nIndex = 0; nIndex < sUnion.uVal.seq8Val.size() / 2; nIndex++) + EXPECT_EQ(sUnion.uVal.seq8Val[nIndex], sUnion.uVal.seq8Val[nIndex + sUnion.uVal.seq8Val.size() / 2]); + sUnion.switch_to(37); + sUnion.uVal.seqssVal.resize(3); + sUnion.uVal.seqssVal[0] = "hello"; + sUnion.uVal.seqssVal[1] = "huhu"; + sUnion.uVal.seqssVal[2] = "hi"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.seqssVal.size(), 6); + for (size_t nIndex = 0; nIndex < sUnion.uVal.seqssVal.size() / 2; nIndex++) + EXPECT_EQ(sUnion.uVal.seqssVal[nIndex], sUnion.uVal.seqssVal[nIndex + sUnion.uVal.seqssVal.size() / 2]); + sUnion.switch_to(38); + sUnion.uVal.seq8FixVal.resize(sUnion.uVal.seq8FixVal.capacity()); + for (size_t nIndex = 0; nIndex < sUnion.uVal.seq8FixVal.size(); nIndex++) + sUnion.uVal.seq8FixVal[nIndex] = static_cast(nIndex); + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.seq8FixVal.size(), 20); + for (size_t nIndex = 0; nIndex < sUnion.uVal.seq8FixVal.size(); nIndex++) + EXPECT_EQ(sUnion.uVal.seq8FixVal[nIndex], static_cast(nIndex << 1)); + sUnion.switch_to(39); + sUnion.uVal.seqssFixVal.resize(sUnion.uVal.seqssFixVal.capacity()); + sUnion.uVal.seqssFixVal[0] = "hi"; + sUnion.uVal.seqssFixVal[1] = "hoi"; + sUnion.uVal.seqssFixVal[2] = "hello"; + sUnion.uVal.seqssFixVal[3] = "hallo"; + sUnion.uVal.seqssFixVal[4] = "servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.seqssFixVal.size(), 5); + EXPECT_EQ(sUnion.uVal.seqssFixVal[0], "hihi"); + EXPECT_EQ(sUnion.uVal.seqssFixVal[1], "hoihoi"); + EXPECT_EQ(sUnion.uVal.seqssFixVal[2], "hellohello"); + EXPECT_EQ(sUnion.uVal.seqssFixVal[3], "hallohallo"); + EXPECT_EQ(sUnion.uVal.seqssFixVal[4], "servusservus"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaVarBasedUnionInterface) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + struct SMultiply : public IMultiplyValue + { + size_t Multiply(size_t n) + { + return n * n; + } + } sMultiply; + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(25); + + sUnion.uVal.idVal = sdv::GetInterfaceId(); + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.idVal, sdv::GetInterfaceId()); + sUnion.switch_to(44); + sUnion.uVal.ifcVal = static_cast(&sMultiply); + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ifcVal.id(), sdv::GetInterfaceId()); + ASSERT_NE(sUnion.uVal.ifcVal, nullptr); + ASSERT_NE(sUnion.uVal.ifcVal.get(), nullptr); + EXPECT_EQ(sUnion.uVal.ifcVal.get()->Add(99), 19602); // 99 * 99 + 99 * 99 + sUnion.switch_to(45); + sUnion.uVal.pMultiplyValue = &sMultiply; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + ASSERT_NE(sUnion.uVal.pMultiplyValue, nullptr); + EXPECT_EQ(sUnion.uVal.pMultiplyValue->Multiply(5), 625); // 5 * 5 * 5 * 5 + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaVarBasedUnionSubCompound) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(46); + sUnion.uVal.sSubVal.i = 1; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.sSubVal.i, 2); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaVarBasedUnionBooleanTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(201); + sUnion.uVal.tbVal = false; + EXPECT_FALSE(sUnion.uVal.tbVal); + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_TRUE(sUnion.uVal.tbVal); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaVarBasedUnionIntegralTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(202); + sUnion.uVal.tsVal = -10; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tsVal, -11); + sUnion.switch_to(203); + sUnion.uVal.tusVal = 10; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tusVal, 11u); + sUnion.switch_to(204); + sUnion.uVal.tlVal = -20; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tlVal, -21); + sUnion.switch_to(205); + sUnion.uVal.tulVal = 20; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tulVal, 21u); + sUnion.switch_to(206); + sUnion.uVal.tllVal = -30; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tllVal, -31); + sUnion.switch_to(207); + sUnion.uVal.tullVal = 30; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tullVal, 31u); + sUnion.switch_to(208); + sUnion.uVal.ti8Val = -40; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(static_cast(sUnion.uVal.ti8Val), static_cast(-41)); // Cast needed for ARM compatibility + sUnion.switch_to(209); + sUnion.uVal.tui8Val = 40; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tui8Val, 41u); + sUnion.switch_to(210); + sUnion.uVal.ti16Val = -50; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ti16Val, -51); + sUnion.switch_to(211); + sUnion.uVal.tui16Val = 50; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tui16Val, 51u); + sUnion.switch_to(212); + sUnion.uVal.ti32Val = -60; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ti32Val, -61); + sUnion.switch_to(213); + sUnion.uVal.tui32Val = 60; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tui32Val, 61u); + sUnion.switch_to(214); + sUnion.uVal.ti64Val = -70; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ti64Val, -71); + sUnion.switch_to(215); + sUnion.uVal.tui64Val = 70; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tui64Val, 71u); + sUnion.switch_to(223); + sUnion.uVal.tnVal = 80; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tnVal, 81); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaVarBasedUnionCharacterTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(216); + sUnion.uVal.tcVal = 'A'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tcVal, 'B'); + sUnion.switch_to(217); + sUnion.uVal.tc16Val = u'D'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tc16Val, u'E'); + sUnion.switch_to(218); + sUnion.uVal.tc32Val = U'G'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tc32Val, U'H'); + sUnion.switch_to(219); + sUnion.uVal.twcVal = L'J'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.twcVal, L'K'); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaVarBasedUnionFloatingPointTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(220); + float fVal = -1234.5678f; + sUnion.uVal.tfVal = fVal; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tfVal, fVal - 1000.000f); + sUnion.switch_to(221); + double dVal = 8765.4321; + sUnion.uVal.tdVal = dVal; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tdVal, dVal + 1000.000); + // GCC issue with generated initialization in the consructor for the "long double" type: BUG #3982727 + //sUnion.switch_to(222); + //long double ldVal = -1234.4321; + //sUnion.uVal.tldVal = ldVal; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + //EXPECT_EQ(sUnion.uVal.tldVal, ldVal - 1000.000); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaVarBasedUnionEnumTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(224); + sUnion.uVal.teHelloVal = EHello::hello; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.teHelloVal, EHello::hallo); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaVarBasedUnionStringTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(226); + sUnion.uVal.tssVal = "hi"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tssVal, "hij"); + sUnion.switch_to(227); + sUnion.uVal.tss8Val = "huhu"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tss8Val, "huhuv"); + sUnion.switch_to(228); + sUnion.uVal.tss16Val = u"hey"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tss16Val, u"heyz"); + sUnion.switch_to(229); + sUnion.uVal.tss32Val = U"hello"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tss32Val, U"hellop"); + sUnion.switch_to(230); + sUnion.uVal.twssVal = L"servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.twssVal, L"servust"); + sUnion.switch_to(231); + sUnion.uVal.tssFixVal = "hi"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tssFixVal, "hij"); + sUnion.switch_to(232); + sUnion.uVal.tss8FixVal = "huhu"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tss8FixVal, "huhuv"); + sUnion.switch_to(233); + sUnion.uVal.tss16FixVal = u"hey"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tss16FixVal, u"heyz"); + sUnion.switch_to(234); + sUnion.uVal.tss32FixVal = U"hello"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tss32FixVal, U"hellop"); + sUnion.switch_to(235); + sUnion.uVal.twssFixVal = L"servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.twssFixVal, L"servust"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +// Using typedefs of template based types produced an error: BUG #398266 +TEST(IPC_Communication_Test_UDS, DISABLED_MarshallMegaVarBasedUnionPointerTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(236); + //sUnion.uVal.tptr8Val.resize(100); + //for (size_t nIndex = 0; nIndex < sUnion.uVal.tptr8Val.size(); nIndex++) + // sUnion.uVal.tptr8Val[nIndex] = static_cast(nIndex); + //EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + //EXPECT_EQ(sUnion.uVal.tptr8Val.size(), 200); + //for (size_t nIndex = 0; nIndex < sUnion.uVal.tptr8Val.size() / 2; nIndex++) + // EXPECT_EQ(sUnion.uVal.tptr8Val[nIndex], sUnion.uVal.tptr8Val[nIndex + sUnion.uVal.tptr8Val.size() / 2]); + //sUnion.switch_to(237); + //sUnion.uVal.tptrssVal.resize(3); + //sUnion.uVal.tptrssVal[0] = "hello"; + //sUnion.uVal.tptrssVal[1] = "huhu"; + //sUnion.uVal.tptrssVal[2] = "hi"; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + //EXPECT_EQ(sUnion.uVal.tptrssVal.size(), 6); + //for (size_t nIndex = 0; nIndex < sUnion.uVal.tptrssVal.size() / 2; nIndex++) + // EXPECT_EQ(sUnion.uVal.tptrssVal[nIndex], sUnion.uVal.tptrssVal[nIndex + sUnion.uVal.tptrssVal.size() / 2]); + //sUnion.switch_to(238); + //sUnion.uVal.tptr8FixVal.resize(sUnion.uVal.tptr8FixVal.capacity()); + //for (size_t nIndex = 0; nIndex < sUnion.uVal.tptr8FixVal.size(); nIndex++) + // sUnion.uVal.tptr8FixVal[nIndex] = static_cast(nIndex); + //EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + //EXPECT_EQ(sUnion.uVal.tptr8FixVal.size(), 20); + //for (size_t nIndex = 0; nIndex < sUnion.uVal.tptr8FixVal.size(); nIndex++) + // EXPECT_EQ(sUnion.uVal.tptr8FixVal[nIndex], static_cast(nIndex << 1)); + //sUnion.switch_to(239); + //sUnion.uVal.tptrssFixVal.resize(sUnion.uVal.tptrssFixVal.capacity()); + //sUnion.uVal.tptrssFixVal[0] = "hi"; + //sUnion.uVal.tptrssFixVal[1] = "hoi"; + //sUnion.uVal.tptrssFixVal[2] = "hello"; + //sUnion.uVal.tptrssFixVal[3] = "hallo"; + //sUnion.uVal.tptrssFixVal[4] = "servus"; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + //EXPECT_EQ(sUnion.uVal.tptrssFixVal.size(), 5); + //EXPECT_EQ(sUnion.uVal.tptrssFixVal[0], "hihi"); + //EXPECT_EQ(sUnion.uVal.tptrssFixVal[1], "hoihoi"); + //EXPECT_EQ(sUnion.uVal.tptrssFixVal[2], "hellohello"); + //EXPECT_EQ(sUnion.uVal.tptrssFixVal[3], "hallohallo"); + //EXPECT_EQ(sUnion.uVal.tptrssFixVal[4], "servusservus"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +// Using typedefs of template based types produced an error: BUG #398266 +TEST(IPC_Communication_Test_UDS, DISABLED_MarshallMegaVarBasedUnionSequenceTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(236); + //sUnion.uVal.tseq8Val.resize(100); + //for (size_t nIndex = 0; nIndex < sUnion.uVal.tseq8Val.size(); nIndex++) + // sUnion.uVal.tseq8Val[nIndex] = static_cast(nIndex); + //EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + //EXPECT_EQ(sUnion.uVal.tseq8Val.size(), 200); + //for (size_t nIndex = 0; nIndex < sUnion.uVal.tseq8Val.size() / 2; nIndex++) + // EXPECT_EQ(sUnion.uVal.tseq8Val[nIndex], sUnion.uVal.tseq8Val[nIndex + sUnion.uVal.tseq8Val.size() / 2]); + //sUnion.switch_to(237); + //sUnion.uVal.tseqssVal.resize(3); + //sUnion.uVal.tseqssVal[0] = "hello"; + //sUnion.uVal.tseqssVal[1] = "huhu"; + //sUnion.uVal.tseqssVal[2] = "hi"; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + //EXPECT_EQ(sUnion.uVal.tseqssVal.size(), 6); + //for (size_t nIndex = 0; nIndex < sUnion.uVal.tseqssVal.size() / 2; nIndex++) + // EXPECT_EQ(sUnion.uVal.tseqssVal[nIndex], sUnion.uVal.tseqssVal[nIndex + sUnion.uVal.tseqssVal.size() / 2]); + //sUnion.switch_to(238); + //sUnion.uVal.tseq8FixVal.resize(sUnion.uVal.tseq8FixVal.capacity()); + //for (size_t nIndex = 0; nIndex < sUnion.uVal.tseq8FixVal.size(); nIndex++) + // sUnion.uVal.tseq8FixVal[nIndex] = static_cast(nIndex); + //EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + //EXPECT_EQ(sUnion.uVal.tseq8FixVal.size(), 20); + //for (size_t nIndex = 0; nIndex < sUnion.uVal.tseq8FixVal.size(); nIndex++) + // EXPECT_EQ(sUnion.uVal.tseq8FixVal[nIndex], static_cast(nIndex << 1)); + //sUnion.switch_to(239); + //sUnion.uVal.tseqssFixVal.resize(sUnion.uVal.tseqssFixVal.capacity()); + //sUnion.uVal.tseqssFixVal[0] = "hi"; + //sUnion.uVal.tseqssFixVal[1] = "hoi"; + //sUnion.uVal.tseqssFixVal[2] = "hello"; + //sUnion.uVal.tseqssFixVal[3] = "hallo"; + //sUnion.uVal.tseqssFixVal[4] = "servus"; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + //EXPECT_EQ(sUnion.uVal.tseqssFixVal.size(), 5); + //EXPECT_EQ(sUnion.uVal.tseqssFixVal[0], "hihi"); + //EXPECT_EQ(sUnion.uVal.tseqssFixVal[1], "hoihoi"); + //EXPECT_EQ(sUnion.uVal.tseqssFixVal[2], "hellohello"); + //EXPECT_EQ(sUnion.uVal.tseqssFixVal[3], "hallohallo"); + //EXPECT_EQ(sUnion.uVal.tseqssFixVal[4], "servusservus"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaVarBasedUnionInterfaceTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + struct SMultiply : public IMultiplyValue + { + size_t Multiply(size_t n) + { + return n * n; + } + } sMultiply; + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(225); + + sUnion.uVal.tidVal = sdv::GetInterfaceId(); + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tidVal, sdv::GetInterfaceId()); + sUnion.switch_to(244); + sUnion.uVal.tifcVal = static_cast(&sMultiply); + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tifcVal.id(), sdv::GetInterfaceId()); + ASSERT_NE(sUnion.uVal.tifcVal, nullptr); + ASSERT_NE(sUnion.uVal.tifcVal.get(), nullptr); + EXPECT_EQ(sUnion.uVal.tifcVal.get()->Add(99), 19602); // 99 * 99 + 99 * 99 + // Typedef of interfaces current not possible. BUG #399464 + //sUnion.switch_to(245); + //sUnion.uVal.tpMultiplyValue = &sMultiply; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + //ASSERT_NE(sUnion.uVal.tpMultiplyValue, nullptr); + //EXPECT_EQ(sUnion.uVal.tpMultiplyValue->Multiply(5), 625); // 5 * 5 * 5 * 5 + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS, MarshallMegaVarBasedUnionSubCompoundTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::sockets); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(246); + sUnion.uVal.tsSubVal.i = 1; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tsSubVal.i, 2); + sUnion.switch_to(247); + sUnion.uVal.tsIndVal.i = 10; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tsIndVal.i, 20); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} diff --git a/tests/unit_tests/ipc_com/ipc_com_uds_tunnel.cpp b/tests/unit_tests/ipc_com/ipc_com_uds_tunnel.cpp new file mode 100644 index 0000000..b96c576 --- /dev/null +++ b/tests/unit_tests/ipc_com/ipc_com_uds_tunnel.cpp @@ -0,0 +1,5534 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + + +#include "include.h" + +namespace +{ +constexpr const char* kTunnelChannelObjectName = "DEFAULT_CHANNEL"; +constexpr const char* kTunnelEndpointConfig = "proto=tunnel;path=DEFAULT_CHANNEL;tunnel=DEFAULT_CHANNEL;"; +} + +TEST(IPC_Communication_Test_UDS_Tunnel, AssignServerEndpoint) +{ + sdv::app::CAppControl appcontrol; + + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[IpcProvider] +Name = "unix_domain_sockets_tunnel" + +[[IpcChannel]] +Name = "DEFAULT_CHANNEL" +Provider = "unix_domain_sockets_tunnel" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + control.SetOperationMode(sdv::EOperationMode::running); + + // Create a channel endpoint + sdv::ipc::ICreateEndpoint* ptrCreateEndpoint = sdv::core::GetObject(kTunnelChannelObjectName); + ASSERT_NE(ptrCreateEndpoint, nullptr); + + sdv::ipc::SChannelEndpoint sChannelEndpoint = ptrCreateEndpoint->CreateEndpoint(kTunnelEndpointConfig); + EXPECT_NE(sChannelEndpoint.pConnection, nullptr); + EXPECT_FALSE(sChannelEndpoint.ssConnectString.empty()); + EXPECT_NE(sChannelEndpoint.ssConnectString.find("proto=tunnel"), std::string::npos); + sdv::TObjectPtr ptrEndpoint = sChannelEndpoint.pConnection; // Automatic lifetime management + EXPECT_TRUE(ptrEndpoint); + + // Assign the server endpoint + CInterfaceTest test; + EXPECT_TRUE(control.AssignServerEndpoint(ptrEndpoint, &test, 100, false) != 0u); + ptrEndpoint.Clear(); // Lifetime taken over by communication control. + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, AssignClientEndpoint) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[IpcProvider] +Name = "unix_domain_sockets_tunnel" + +[[IpcChannel]] +Name = "DEFAULT_CHANNEL" +Provider = "unix_domain_sockets_tunnel" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + control.SetOperationMode(sdv::EOperationMode::running); + + // Create a server endpoint + sdv::ipc::ICreateEndpoint* ptrCreateEndpoint = sdv::core::GetObject(kTunnelChannelObjectName); + ASSERT_NE(ptrCreateEndpoint, nullptr); + + sdv::ipc::SChannelEndpoint sChannelEndpoint = ptrCreateEndpoint->CreateEndpoint(kTunnelEndpointConfig); + EXPECT_NE(sChannelEndpoint.pConnection, nullptr); + EXPECT_FALSE(sChannelEndpoint.ssConnectString.empty()); + sdv::TObjectPtr ptrServerEndpoint = sChannelEndpoint.pConnection; // Automatic lifetime management + EXPECT_TRUE(ptrServerEndpoint); + + // Create a client endpoint + sdv::ipc::IChannelAccess* pChannelAccess = sdv::core::GetObject(kTunnelChannelObjectName); + ASSERT_NE(pChannelAccess, nullptr); + sdv::TObjectPtr ptrClientEndpoint = pChannelAccess->Access(sChannelEndpoint.ssConnectString); + EXPECT_TRUE(ptrClientEndpoint); + + // Assign the server and client endpoints + CInterfaceTest test; + EXPECT_TRUE(control.AssignServerEndpoint(ptrServerEndpoint, &test, 100, false) != 0u); + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.AssignClientEndpoint(ptrClientEndpoint, 1000, pObjectProxy) != 0u); + EXPECT_NE(pObjectProxy, nullptr); + ptrServerEndpoint.Clear(); // Lifetime taken over by communication control. + ptrClientEndpoint.Clear(); // Lifetime taken over by communication control. + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, AssignClientEndpoint_Reconnect) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[IpcProvider] +Name = "unix_domain_sockets_tunnel" + +[[IpcChannel]] +Name = "DEFAULT_CHANNEL" +Provider = "unix_domain_sockets_tunnel" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + control.SetOperationMode(sdv::EOperationMode::running); + + // Create a server endpoint + sdv::ipc::ICreateEndpoint* ptrCreateEndpoint = sdv::core::GetObject(kTunnelChannelObjectName); + ASSERT_NE(ptrCreateEndpoint, nullptr); + + sdv::ipc::SChannelEndpoint sChannelEndpoint = ptrCreateEndpoint->CreateEndpoint(kTunnelEndpointConfig); + EXPECT_NE(sChannelEndpoint.pConnection, nullptr); + EXPECT_FALSE(sChannelEndpoint.ssConnectString.empty()); + sdv::TObjectPtr ptrServerEndpoint = sChannelEndpoint.pConnection; // Automatic lifetime management + EXPECT_TRUE(ptrServerEndpoint); + + // Create a client endpoint + sdv::ipc::IChannelAccess* pChannelAccess = sdv::core::GetObject(kTunnelChannelObjectName); + ASSERT_NE(pChannelAccess, nullptr); + sdv::TObjectPtr ptrClientEndpoint = pChannelAccess->Access(sChannelEndpoint.ssConnectString); + EXPECT_TRUE(ptrClientEndpoint); + + // Assign the server and client endpoints + CInterfaceTest test; + EXPECT_TRUE(control.AssignServerEndpoint(ptrServerEndpoint, &test, 100, true) != 0u); + ptrServerEndpoint.Clear(); // Lifetime taken over by communication control. + sdv::IInterfaceAccess* pObjectProxy = nullptr; + sdv::com::TConnectionID tConnectionID = control.AssignClientEndpoint(ptrClientEndpoint, 1000, pObjectProxy); + EXPECT_TRUE(tConnectionID != 0u); + EXPECT_NE(pObjectProxy, nullptr); + + // Disconnect from the client + sdv::ipc::IConnect* pClientConnect = ptrClientEndpoint.GetInterface(); + ASSERT_NE(pClientConnect, nullptr); + EXPECT_EQ(pClientConnect->GetConnectState(), sdv::ipc::EConnectState::connected); + control.RemoveConnection(tConnectionID); + EXPECT_EQ(pClientConnect->GetConnectState(), sdv::ipc::EConnectState::disconnected); + ptrClientEndpoint.Clear(); // Lifetime taken over by communication control. + + // Create another client endpoint + ptrClientEndpoint = pChannelAccess->Access(sChannelEndpoint.ssConnectString); + EXPECT_TRUE(ptrClientEndpoint); + + // Assign the new client endpoint + pObjectProxy = nullptr; + tConnectionID = control.AssignClientEndpoint(ptrClientEndpoint, 1000, pObjectProxy); + EXPECT_TRUE(tConnectionID != 0u); + EXPECT_NE(pObjectProxy, nullptr); + ptrClientEndpoint.Clear(); // Lifetime taken over by communication control. + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, AssignClientEndpoint_FailReconnect) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[IpcProvider] +Name = "unix_domain_sockets_tunnel" + +[[IpcChannel]] +Name = "DEFAULT_CHANNEL" +Provider = "unix_domain_sockets_tunnel" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + control.SetOperationMode(sdv::EOperationMode::running); + + // Create a server endpoint + sdv::ipc::ICreateEndpoint* ptrCreateEndpoint = sdv::core::GetObject(kTunnelChannelObjectName); + ASSERT_NE(ptrCreateEndpoint, nullptr); + + sdv::ipc::SChannelEndpoint sChannelEndpoint = ptrCreateEndpoint->CreateEndpoint(kTunnelEndpointConfig); + EXPECT_NE(sChannelEndpoint.pConnection, nullptr); + EXPECT_FALSE(sChannelEndpoint.ssConnectString.empty()); + sdv::TObjectPtr ptrServerEndpoint = sChannelEndpoint.pConnection; // Automatic lifetime management + EXPECT_TRUE(ptrServerEndpoint); + + // Create a client endpoint + sdv::ipc::IChannelAccess* pChannelAccess = sdv::core::GetObject(kTunnelChannelObjectName); + ASSERT_NE(pChannelAccess, nullptr); + sdv::TObjectPtr ptrClientEndpoint = pChannelAccess->Access(sChannelEndpoint.ssConnectString); + EXPECT_TRUE(ptrClientEndpoint); + + // Assign the server and client endpoints + CInterfaceTest test; + EXPECT_TRUE(control.AssignServerEndpoint(ptrServerEndpoint, &test, 100, false) != 0u); + ptrServerEndpoint.Clear(); // Lifetime taken over by communication control. + sdv::IInterfaceAccess* pObjectProxy = nullptr; + sdv::com::TConnectionID tConnectionID = control.AssignClientEndpoint(ptrClientEndpoint, 1000, pObjectProxy); + EXPECT_TRUE(tConnectionID != 0u); + EXPECT_NE(pObjectProxy, nullptr); + + // Disconnect from the client + sdv::ipc::IConnect* pClientConnect = ptrClientEndpoint.GetInterface(); + ASSERT_NE(pClientConnect, nullptr); + EXPECT_EQ(pClientConnect->GetConnectState(), sdv::ipc::EConnectState::connected); + control.RemoveConnection(tConnectionID); + EXPECT_EQ(pClientConnect->GetConnectState(), sdv::ipc::EConnectState::disconnected); + ptrClientEndpoint.Clear(); // Lifetime taken over by communication control. + + // Create another client endpoint + ptrClientEndpoint = pChannelAccess->Access(sChannelEndpoint.ssConnectString); + + // Assign the new client endpoint - this should fail. + // On tunnel backends, Access may return nullptr or succeed but AssignClientEndpoint fails. + pObjectProxy = nullptr; + if (ptrClientEndpoint) + { + tConnectionID = control.AssignClientEndpoint(ptrClientEndpoint, 1000, pObjectProxy); + } + else + { + tConnectionID = sdv::com::TConnectionID{}; + } + + // Verify reconnect failed (either at Access or at AssignClientEndpoint) + EXPECT_TRUE(tConnectionID.uiControl == 0u); + EXPECT_TRUE(tConnectionID.uiIdent == 0u); + EXPECT_EQ(pObjectProxy, nullptr); + ptrClientEndpoint.Clear(); // Lifetime taken over by communication control. + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, CreateServerConnection) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, CreateClientConnection) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + EXPECT_NE(pObjectProxy, nullptr); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, CreateClientConnection_FailReconnect) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + sdv::com::TConnectionID tConnectionID = control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy); + EXPECT_TRUE(tConnectionID != 0u); + EXPECT_NE(pObjectProxy, nullptr); + + // Disconnect from the client + control.RemoveConnection(tConnectionID); + + // Create a new client connection + pObjectProxy = nullptr; + tConnectionID = control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy); + EXPECT_TRUE(tConnectionID == 0u); + EXPECT_EQ(pObjectProxy, nullptr); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallClientToServer) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Request the ISayHello interface + ISayHello* pSayHello = nullptr; + EXPECT_NO_THROW(pSayHello = pObjectProxy->GetInterface()); + ASSERT_NE(pSayHello, nullptr); + + EXPECT_EQ(pSayHello->Hello(), "Hello"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallInterfaceFromServer) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Request the IRequestHello interface + IRequestHello* pRequestHello = nullptr; + EXPECT_NO_THROW(pRequestHello = pObjectProxy->GetInterface()); + ASSERT_NE(pRequestHello, nullptr); + + // Request the ISayHello interface + ISayHello* pSayHello = pRequestHello->Request(); + + // Call the interface as if talking from the client + ASSERT_NE(pSayHello, nullptr); + EXPECT_EQ(pSayHello->Hello(), "Hello"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallInterfaceFromClient) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Request the IRegisterHelloCallback interface + IRegisterHelloCallback* pRegisterHelloCallback = nullptr; + EXPECT_NO_THROW(pRegisterHelloCallback = pObjectProxy->GetInterface()); + ASSERT_NE(pRegisterHelloCallback, nullptr); + + // Register the callback (stored in the object) + pRegisterHelloCallback->Register(&test); + + // Call the interface as if talking from the server + ASSERT_NE(test.m_pHello, nullptr); + EXPECT_EQ(test.m_pHello->Hello(), "Hello"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallGenericInterfaceFromServer) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Request the IRegisterHelloCallback interface + sdv::IInterfaceAccess* pRegisterHelloCallback = nullptr; + EXPECT_NO_THROW(pRegisterHelloCallback = pObjectProxy->GetInterface()); + ASSERT_NE(pRegisterHelloCallback, nullptr); + + // Call GetInterface to get the IInterfaceAccess pointer once more (to be able to test marshalling). + sdv::IInterfaceAccess* pInterfaceAccess = pObjectProxy->GetInterface(); + ASSERT_NE(pInterfaceAccess, nullptr); + + // Request the pSayHello interface (this will be marshalled as well) + ISayHello* pSayHello = pInterfaceAccess->GetInterface(); + + // Call the interface as if talking from the client + ASSERT_NE(pSayHello, nullptr); + EXPECT_EQ(pSayHello->Hello(), "Hello"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaStructBoolean) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.bVal = false; + EXPECT_FALSE(sStruct.bVal); + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_TRUE(sStruct.bVal); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaStructIntegral) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.sVal = -10; + sStruct.usVal = 10; + sStruct.lVal = -20; + sStruct.ulVal = 20; + sStruct.llVal = -30; + sStruct.ullVal = 30; + sStruct.i8Val = -40; + sStruct.ui8Val = 40; + sStruct.i16Val = -50; + sStruct.ui16Val = 50; + sStruct.i32Val = -60; + sStruct.ui32Val = 60; + sStruct.i64Val = -70; + sStruct.ui64Val = 70; + sStruct.nVal = 80; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.sVal, -11); + EXPECT_EQ(sStruct.usVal, 11u); + EXPECT_EQ(sStruct.lVal, -21); + EXPECT_EQ(sStruct.ulVal, 21u); + EXPECT_EQ(sStruct.llVal, -31); + EXPECT_EQ(sStruct.ullVal, 31u); + EXPECT_EQ(static_cast(sStruct.i8Val), static_cast(-41)); // Cast needed for ARM compatibility + EXPECT_EQ(sStruct.ui8Val, 41u); + EXPECT_EQ(sStruct.i16Val, -51); + EXPECT_EQ(sStruct.ui16Val, 51u); + EXPECT_EQ(sStruct.i32Val, -61); + EXPECT_EQ(sStruct.ui32Val, 61u); + EXPECT_EQ(sStruct.i64Val, -71); + EXPECT_EQ(sStruct.ui64Val, 71u); + EXPECT_EQ(sStruct.nVal, 81); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaStructCharacter) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.cVal = 'A'; + sStruct.c16Val = u'D'; + sStruct.c32Val = U'G'; + sStruct.wcVal = L'J'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.cVal, 'B'); + EXPECT_EQ(sStruct.c16Val, u'E'); + EXPECT_EQ(sStruct.c32Val, U'H'); + EXPECT_EQ(sStruct.wcVal, L'K'); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaStructFloatingPoint) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + float fVal = -1234.5678f; + sStruct.fVal = fVal; + double dVal = 8765.4321; + sStruct.dVal = dVal; + long double ldVal = -1234.4321; + sStruct.ldVal = ldVal; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.fVal, fVal - 1000.000f); + EXPECT_EQ(sStruct.dVal, dVal + 1000.000); + EXPECT_EQ(sStruct.ldVal, ldVal - 1000.000); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaStructEnum) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.eHelloVal = EHello::hello; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.eHelloVal, EHello::hallo); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaStructString) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.ssVal = "hi"; + sStruct.ss8Val = "huhu"; + sStruct.ss16Val = u"hey"; + sStruct.ss32Val = U"hello"; + sStruct.wssVal = L"servus"; + sStruct.ssFixVal = "hi"; + sStruct.ss8FixVal = "huhu"; + sStruct.ss16FixVal = u"hey"; + sStruct.ss32FixVal = U"hello"; + sStruct.wssFixVal = L"servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.ssVal, "hij"); + EXPECT_EQ(sStruct.ss8Val, "huhuv"); + EXPECT_EQ(sStruct.ss16Val, u"heyz"); + EXPECT_EQ(sStruct.ss32Val, U"hellop"); + EXPECT_EQ(sStruct.wssVal, L"servust"); + EXPECT_EQ(sStruct.ssFixVal, "hij"); + EXPECT_EQ(sStruct.ss8FixVal, "huhuv"); + EXPECT_EQ(sStruct.ss16FixVal, u"heyz"); + EXPECT_EQ(sStruct.ss32FixVal, U"hellop"); + EXPECT_EQ(sStruct.wssFixVal, L"servust"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaStructPointer) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.ptr8Val.resize(100); + for (size_t nIndex = 0; nIndex < sStruct.ptr8Val.size(); nIndex++) + sStruct.ptr8Val[nIndex] = static_cast(nIndex); + sStruct.ptrssVal.resize(3); + sStruct.ptrssVal[0] = "hello"; + sStruct.ptrssVal[1] = "huhu"; + sStruct.ptrssVal[2] = "hi"; + sStruct.ptr8FixVal.resize(sStruct.ptr8FixVal.capacity()); + for (size_t nIndex = 0; nIndex < sStruct.ptr8FixVal.size(); nIndex++) + sStruct.ptr8FixVal[nIndex] = static_cast(nIndex); + sStruct.ptrssFixVal.resize(sStruct.ptrssFixVal.capacity()); + sStruct.ptrssFixVal[0] = "hi"; + sStruct.ptrssFixVal[1] = "hoi"; + sStruct.ptrssFixVal[2] = "hello"; + sStruct.ptrssFixVal[3] = "hallo"; + sStruct.ptrssFixVal[4] = "servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.ptr8Val.size(), 200); + for (size_t nIndex = 0; nIndex < sStruct.ptr8Val.size() / 2; nIndex++) + EXPECT_EQ(sStruct.ptr8Val[nIndex], sStruct.ptr8Val[nIndex + sStruct.ptr8Val.size() / 2]); + EXPECT_EQ(sStruct.ptrssVal.size(), 6); + for (size_t nIndex = 0; nIndex < sStruct.ptrssVal.size() / 2; nIndex++) + EXPECT_EQ(sStruct.ptrssVal[nIndex], sStruct.ptrssVal[nIndex + sStruct.ptrssVal.size() / 2]); + EXPECT_EQ(sStruct.ptr8FixVal.size(), 20); + for (size_t nIndex = 0; nIndex < sStruct.ptr8FixVal.size(); nIndex++) + EXPECT_EQ(sStruct.ptr8FixVal[nIndex], static_cast(nIndex << 1)); + EXPECT_EQ(sStruct.ptrssFixVal.size(), 5); + EXPECT_EQ(sStruct.ptrssFixVal[0], "hihi"); + EXPECT_EQ(sStruct.ptrssFixVal[1], "hoihoi"); + EXPECT_EQ(sStruct.ptrssFixVal[2], "hellohello"); + EXPECT_EQ(sStruct.ptrssFixVal[3], "hallohallo"); + EXPECT_EQ(sStruct.ptrssFixVal[4], "servusservus"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaStructSequence) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.seq8Val.resize(100); + for (size_t nIndex = 0; nIndex < sStruct.seq8Val.size(); nIndex++) + sStruct.seq8Val[nIndex] = static_cast(nIndex); + sStruct.seqssVal.resize(3); + sStruct.seqssVal[0] = "hello"; + sStruct.seqssVal[1] = "huhu"; + sStruct.seqssVal[2] = "hi"; + sStruct.seq8FixVal.resize(sStruct.seq8FixVal.capacity()); + for (size_t nIndex = 0; nIndex < sStruct.seq8FixVal.size(); nIndex++) + sStruct.seq8FixVal[nIndex] = static_cast(nIndex); + sStruct.seqssFixVal.resize(sStruct.seqssFixVal.capacity()); + sStruct.seqssFixVal[0] = "hi"; + sStruct.seqssFixVal[1] = "hoi"; + sStruct.seqssFixVal[2] = "hello"; + sStruct.seqssFixVal[3] = "hallo"; + sStruct.seqssFixVal[4] = "servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.seq8Val.size(), 200); + for (size_t nIndex = 0; nIndex < sStruct.seq8Val.size() / 2; nIndex++) + EXPECT_EQ(sStruct.seq8Val[nIndex], sStruct.seq8Val[nIndex + sStruct.seq8Val.size() / 2]); + EXPECT_EQ(sStruct.seqssVal.size(), 6); + for (size_t nIndex = 0; nIndex < sStruct.seqssVal.size() / 2; nIndex++) + EXPECT_EQ(sStruct.seqssVal[nIndex], sStruct.seqssVal[nIndex + sStruct.seqssVal.size() / 2]); + EXPECT_EQ(sStruct.seq8FixVal.size(), 20); + for (size_t nIndex = 0; nIndex < sStruct.seq8FixVal.size(); nIndex++) + EXPECT_EQ(sStruct.seq8FixVal[nIndex], static_cast(nIndex << 1)); + EXPECT_EQ(sStruct.seqssFixVal.size(), 5); + EXPECT_EQ(sStruct.seqssFixVal[0], "hihi"); + EXPECT_EQ(sStruct.seqssFixVal[1], "hoihoi"); + EXPECT_EQ(sStruct.seqssFixVal[2], "hellohello"); + EXPECT_EQ(sStruct.seqssFixVal[3], "hallohallo"); + EXPECT_EQ(sStruct.seqssFixVal[4], "servusservus"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaStructInterface) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + struct SMultiply : public IMultiplyValue + { + size_t Multiply(size_t n) + { + return n * n; + } + } sMultiply; + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + + sStruct.idVal = sdv::GetInterfaceId(); + sStruct.ifcVal = static_cast(&sMultiply); + sStruct.pMultiplyValue = &sMultiply; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.idVal, sdv::GetInterfaceId()); + EXPECT_EQ(sStruct.ifcVal.id(), sdv::GetInterfaceId()); + ASSERT_NE(sStruct.ifcVal, nullptr); + ASSERT_NE(sStruct.ifcVal.get(), nullptr); + EXPECT_EQ(sStruct.ifcVal.get()->Add(99), 19602); // 99 * 99 + 99 * 99 + ASSERT_NE(sStruct.pMultiplyValue, nullptr); + EXPECT_EQ(sStruct.pMultiplyValue->Multiply(5), 625); // 5 * 5 * 5 * 5 + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaStructSubCompound) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.sSubVal.i = 1; + sStruct.sIndVal.i = 10; + //sStruct.sUnnamedVal.i = 100; // 12.04.2024 EVE Unnamed structures are not supported by IDL compiler. + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.sSubVal.i, 2); + EXPECT_EQ(sStruct.sIndVal.i, 20); + //EXPECT_EQ(sStruct.sUnnamedVal.i, 120); // 12.04.2024 EVE Unnamed structures are not supported by IDL compiler. + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaStructBooleanArray) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.rgbVal[0] = false; + sStruct.rgbVal[1] = true; + EXPECT_FALSE(sStruct.bVal); + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_TRUE(sStruct.rgbVal[0]); + EXPECT_FALSE(sStruct.rgbVal[1]); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaStructIntegralArray) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.rgsVal[0] = -10; + sStruct.rgsVal[1] = -11; + sStruct.rgusVal[0] = 10; + sStruct.rgusVal[1] = 11; + sStruct.rglVal[0] = -20; + sStruct.rglVal[1] = -21; + sStruct.rgulVal[0] = 20; + sStruct.rgulVal[1] = 21; + sStruct.rgllVal[0] = -30; + sStruct.rgllVal[1] = -31; + sStruct.rgullVal[0] = 30; + sStruct.rgullVal[1] = 31; + sStruct.rgi8Val[0] = -40; + sStruct.rgi8Val[1] = -41; + sStruct.rgui8Val[0] = 40; + sStruct.rgui8Val[1] = 41; + sStruct.rgi16Val[0] = -50; + sStruct.rgi16Val[1] = -51; + sStruct.rgui16Val[0] = 50; + sStruct.rgui16Val[1] = 51; + sStruct.rgi32Val[0] = -60; + sStruct.rgi32Val[1] = -61; + sStruct.rgui32Val[0] = 60; + sStruct.rgui32Val[1] = 61; + sStruct.rgi64Val[0] = -70; + sStruct.rgi64Val[1] = -71; + sStruct.rgui64Val[0] = 70; + sStruct.rgui64Val[1] = 71; + sStruct.rgnVal[0] = 80; + sStruct.rgnVal[1] = 81; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.rgsVal[0], -11); + EXPECT_EQ(sStruct.rgsVal[1], -10); + EXPECT_EQ(sStruct.rgusVal[0], 11u); + EXPECT_EQ(sStruct.rgusVal[1], 13u); + EXPECT_EQ(sStruct.rglVal[0], -21); + EXPECT_EQ(sStruct.rglVal[1], -20); + EXPECT_EQ(sStruct.rgulVal[0], 21u); + EXPECT_EQ(sStruct.rgulVal[1], 23u); + EXPECT_EQ(sStruct.rgllVal[0], -31); + EXPECT_EQ(sStruct.rgllVal[1], -30); + EXPECT_EQ(sStruct.rgullVal[0], 31u); + EXPECT_EQ(sStruct.rgullVal[1], 33u); + EXPECT_EQ(static_cast(sStruct.rgi8Val[0]), static_cast(-41)); // Cast needed for ARM compatibility + EXPECT_EQ(static_cast(sStruct.rgi8Val[1]), static_cast(-40)); // Cast needed for ARM compatibility + EXPECT_EQ(sStruct.rgui8Val[0], 41u); + EXPECT_EQ(sStruct.rgui8Val[1], 43u); + EXPECT_EQ(sStruct.rgi16Val[0], -51); + EXPECT_EQ(sStruct.rgi16Val[1], -50); + EXPECT_EQ(sStruct.rgui16Val[0], 51u); + EXPECT_EQ(sStruct.rgui16Val[1], 53u); + EXPECT_EQ(sStruct.rgi32Val[0], -61); + EXPECT_EQ(sStruct.rgi32Val[1], -60); + EXPECT_EQ(sStruct.rgui32Val[0], 61u); + EXPECT_EQ(sStruct.rgui32Val[1], 63u); + EXPECT_EQ(sStruct.rgi64Val[0], -71); + EXPECT_EQ(sStruct.rgi64Val[1], -70); + EXPECT_EQ(sStruct.rgui64Val[0], 71u); + EXPECT_EQ(sStruct.rgui64Val[1], 73u); + EXPECT_EQ(sStruct.rgnVal[0], 81); + EXPECT_EQ(sStruct.rgnVal[1], 83); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaStructCharacterArray) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.rgcVal[0] = 'A'; + sStruct.rgcVal[1] = 'B'; + sStruct.rgc16Val[0] = u'D'; + sStruct.rgc16Val[1] = u'E'; + sStruct.rgc32Val[0] = U'G'; + sStruct.rgc32Val[1] = U'H'; + sStruct.rgwcVal[0] = L'J'; + sStruct.rgwcVal[1] = L'K'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.rgcVal[0], 'B'); + EXPECT_EQ(sStruct.rgcVal[1], 'D'); + EXPECT_EQ(sStruct.rgc16Val[0], u'E'); + EXPECT_EQ(sStruct.rgc16Val[1], u'G'); + EXPECT_EQ(sStruct.rgc32Val[0], U'H'); + EXPECT_EQ(sStruct.rgc32Val[1], U'J'); + EXPECT_EQ(sStruct.rgwcVal[0], L'K'); + EXPECT_EQ(sStruct.rgwcVal[1], L'M'); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaStructFloatingPointArray) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + float fVal = -1234.5678f; + sStruct.rgfVal[0] = fVal; + sStruct.rgfVal[1] = fVal; + double dVal = 8765.4321; + sStruct.rgdVal[0] = dVal; + sStruct.rgdVal[1] = dVal; + long double ldVal = -1234.4321; + sStruct.rgldVal[0] = ldVal; + sStruct.rgldVal[1] = ldVal; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.rgfVal[0], fVal - 1000.000f); + EXPECT_EQ(sStruct.rgfVal[1], fVal + 2000.000f); + EXPECT_EQ(sStruct.rgdVal[0], dVal + 1000.000); + EXPECT_EQ(sStruct.rgdVal[1], dVal - 2000.000); + EXPECT_EQ(sStruct.rgldVal[0], ldVal - 1000.000); + EXPECT_EQ(sStruct.rgldVal[1], ldVal + 2000.000); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaStructEnumArray) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.rgeHelloVal[0] = EHello::hello; + sStruct.rgeHelloVal[1] = EHello::hello; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.rgeHelloVal[0], EHello::hallo); + EXPECT_EQ(sStruct.rgeHelloVal[1], EHello::huhu); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaStructStringArray) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.rgssVal[0] = "hi"; + sStruct.rgssVal[1] = "hi"; + sStruct.rgss8Val[0] = "huhu"; + sStruct.rgss8Val[1] = "huhu"; + sStruct.rgss16Val[0] = u"hey"; + sStruct.rgss16Val[1] = u"hey"; + sStruct.rgss32Val[0] = U"hello"; + sStruct.rgss32Val[1] = U"hello"; + sStruct.rgwssVal[0] = L"servus"; + sStruct.rgwssVal[1] = L"servus"; + sStruct.rgssFixVal[0] = "hi"; + sStruct.rgssFixVal[1] = "hi"; + sStruct.rgss8FixVal[0] = "huhu"; + sStruct.rgss8FixVal[1] = "huhu"; + sStruct.rgss16FixVal[0] = u"hey"; + sStruct.rgss16FixVal[1] = u"hey"; + sStruct.rgss32FixVal[0] = U"hello"; + sStruct.rgss32FixVal[1] = U"hello"; + sStruct.rgwssFixVal[0] = L"servus"; + sStruct.rgwssFixVal[1] = L"servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.rgssVal[0], "hij"); + EXPECT_EQ(sStruct.rgssVal[1], "hii"); + EXPECT_EQ(sStruct.rgss8Val[0], "huhuv"); + EXPECT_EQ(sStruct.rgss8Val[1], "huhui"); + EXPECT_EQ(sStruct.rgss16Val[0], u"heyz"); + EXPECT_EQ(sStruct.rgss16Val[1], u"heyi"); + EXPECT_EQ(sStruct.rgss32Val[0], U"hellop"); + EXPECT_EQ(sStruct.rgss32Val[1], U"helloi"); + EXPECT_EQ(sStruct.rgwssVal[0], L"servust"); + EXPECT_EQ(sStruct.rgwssVal[1], L"servust"); + EXPECT_EQ(sStruct.rgssFixVal[0], "hij"); + EXPECT_EQ(sStruct.rgssFixVal[1], "hii"); + EXPECT_EQ(sStruct.rgss8FixVal[0], "huhuv"); + EXPECT_EQ(sStruct.rgss8FixVal[1], "huhui"); + EXPECT_EQ(sStruct.rgss16FixVal[0], u"heyz"); + EXPECT_EQ(sStruct.rgss16FixVal[1], u"heyi"); + EXPECT_EQ(sStruct.rgss32FixVal[0], U"hellop"); + EXPECT_EQ(sStruct.rgss32FixVal[1], U"helloi"); + EXPECT_EQ(sStruct.rgwssFixVal[0], L"servust"); + EXPECT_EQ(sStruct.rgwssFixVal[1], L"servust"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaStructPointerArray) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.rgptr8Val[0].resize(100); + sStruct.rgptr8Val[1].resize(100); + for (size_t nIndex = 0; nIndex < sStruct.rgptr8Val[0].size(); nIndex++) + sStruct.rgptr8Val[0][nIndex] = static_cast(nIndex); + for (size_t nIndex = 0; nIndex < sStruct.rgptr8Val[1].size(); nIndex++) + sStruct.rgptr8Val[1][nIndex] = static_cast(nIndex); + sStruct.rgptrssVal[0].resize(3); + sStruct.rgptrssVal[1].resize(3); + sStruct.rgptrssVal[0][0] = "hello"; + sStruct.rgptrssVal[1][0] = "ohhello"; + sStruct.rgptrssVal[0][1] = "huhu"; + sStruct.rgptrssVal[1][1] = "ohhuhu"; + sStruct.rgptrssVal[0][2] = "hi"; + sStruct.rgptrssVal[1][2] = "ohhi"; + sStruct.rgptr8FixVal[0].resize(sStruct.rgptr8FixVal[0].capacity()); + sStruct.rgptr8FixVal[1].resize(sStruct.rgptr8FixVal[1].capacity()); + for (size_t nIndex = 0; nIndex < sStruct.rgptr8FixVal[0].size(); nIndex++) + sStruct.rgptr8FixVal[0][nIndex] = static_cast(nIndex); + for (size_t nIndex = 0; nIndex < sStruct.rgptr8FixVal[1].size(); nIndex++) + sStruct.rgptr8FixVal[1][nIndex] = static_cast(nIndex); + sStruct.rgptrssFixVal[0].resize(sStruct.rgptrssFixVal[0].capacity()); + sStruct.rgptrssFixVal[1].resize(sStruct.rgptrssFixVal[1].capacity()); + sStruct.rgptrssFixVal[0][0] = "hi"; + sStruct.rgptrssFixVal[1][0] = "ohhi"; + sStruct.rgptrssFixVal[0][1] = "hoi"; + sStruct.rgptrssFixVal[1][1] = "ohhoi"; + sStruct.rgptrssFixVal[0][2] = "hello"; + sStruct.rgptrssFixVal[1][2] = "ohhello"; + sStruct.rgptrssFixVal[0][3] = "hallo"; + sStruct.rgptrssFixVal[1][3] = "ohhallo"; + sStruct.rgptrssFixVal[0][4] = "servus"; + sStruct.rgptrssFixVal[1][4] = "ohservus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.rgptr8Val[0].size(), 200); + EXPECT_EQ(sStruct.rgptr8Val[1].size(), 300); + for (size_t nIndex = 0; nIndex < sStruct.rgptr8Val[0].size() / 2; nIndex++) + EXPECT_EQ(sStruct.rgptr8Val[0][nIndex], sStruct.rgptr8Val[0][nIndex + sStruct.rgptr8Val[0].size() / 2]); + for (size_t nIndex = 0; nIndex < sStruct.rgptr8Val[1].size() / 3; nIndex++) + EXPECT_EQ(sStruct.rgptr8Val[1][nIndex], sStruct.rgptr8Val[1][nIndex + sStruct.rgptr8Val[1].size() / 3]); + EXPECT_EQ(sStruct.rgptrssVal[0].size(), 6); + EXPECT_EQ(sStruct.rgptrssVal[1].size(), 9); + for (size_t nIndex = 0; nIndex < sStruct.rgptrssVal[0].size() / 2; nIndex++) + EXPECT_EQ(sStruct.rgptrssVal[0][nIndex], sStruct.rgptrssVal[0][nIndex + sStruct.rgptrssVal[0].size() / 2]); + for (size_t nIndex = 0; nIndex < sStruct.rgptrssVal[1].size() / 3; nIndex++) + EXPECT_EQ(sStruct.rgptrssVal[1][nIndex], sStruct.rgptrssVal[1][nIndex + sStruct.rgptrssVal[1].size() / 3]); + EXPECT_EQ(sStruct.rgptr8FixVal[0].size(), 20); + EXPECT_EQ(sStruct.rgptr8FixVal[1].size(), 20); + for (size_t nIndex = 0; nIndex < sStruct.rgptr8FixVal[0].size(); nIndex++) + EXPECT_EQ(sStruct.rgptr8FixVal[0][nIndex], static_cast(nIndex << 1)); + for (size_t nIndex = 0; nIndex < sStruct.rgptr8FixVal[1].size(); nIndex++) + EXPECT_EQ(sStruct.rgptr8FixVal[1][nIndex], static_cast(nIndex << 1)); + EXPECT_EQ(sStruct.rgptrssFixVal[0].size(), 5); + EXPECT_EQ(sStruct.rgptrssFixVal[1].size(), 5); + EXPECT_EQ(sStruct.rgptrssFixVal[0][0], "hihi"); + EXPECT_EQ(sStruct.rgptrssFixVal[1][0], "ohhiohhi"); + EXPECT_EQ(sStruct.rgptrssFixVal[0][1], "hoihoi"); + EXPECT_EQ(sStruct.rgptrssFixVal[1][1], "ohhoiohhoi"); + EXPECT_EQ(sStruct.rgptrssFixVal[0][2], "hellohello"); + EXPECT_EQ(sStruct.rgptrssFixVal[1][2], "ohhelloohhello"); + EXPECT_EQ(sStruct.rgptrssFixVal[0][3], "hallohallo"); + EXPECT_EQ(sStruct.rgptrssFixVal[1][3], "ohhalloohhallo"); + EXPECT_EQ(sStruct.rgptrssFixVal[0][4], "servusservus"); + EXPECT_EQ(sStruct.rgptrssFixVal[1][4], "ohservusohservus"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaStructSequenceArray) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.rgseq8Val[0].resize(100); + sStruct.rgseq8Val[1].resize(100); + for (size_t nIndex = 0; nIndex < sStruct.rgseq8Val[0].size(); nIndex++) + sStruct.rgseq8Val[0][nIndex] = static_cast(nIndex); + for (size_t nIndex = 0; nIndex < sStruct.rgseq8Val[1].size(); nIndex++) + sStruct.rgseq8Val[1][nIndex] = static_cast(nIndex); + sStruct.rgseqssVal[0].resize(3); + sStruct.rgseqssVal[1].resize(3); + sStruct.rgseqssVal[0][0] = "hello"; + sStruct.rgseqssVal[1][0] = "ohhello"; + sStruct.rgseqssVal[0][1] = "huhu"; + sStruct.rgseqssVal[1][1] = "ohhuhu"; + sStruct.rgseqssVal[0][2] = "hi"; + sStruct.rgseqssVal[1][2] = "ohhi"; + sStruct.rgseq8FixVal[0].resize(sStruct.rgseq8FixVal[0].capacity()); + sStruct.rgseq8FixVal[1].resize(sStruct.rgseq8FixVal[1].capacity()); + for (size_t nIndex = 0; nIndex < sStruct.rgseq8FixVal[0].size(); nIndex++) + sStruct.rgseq8FixVal[0][nIndex] = static_cast(nIndex); + for (size_t nIndex = 0; nIndex < sStruct.rgseq8FixVal[1].size(); nIndex++) + sStruct.rgseq8FixVal[1][nIndex] = static_cast(nIndex); + sStruct.rgseqssFixVal[0].resize(sStruct.seqssFixVal.capacity()); + sStruct.rgseqssFixVal[1].resize(sStruct.seqssFixVal.capacity()); + sStruct.rgseqssFixVal[0][0] = "hi"; + sStruct.rgseqssFixVal[1][0] = "ohhi"; + sStruct.rgseqssFixVal[0][1] = "hoi"; + sStruct.rgseqssFixVal[1][1] = "ohhoi"; + sStruct.rgseqssFixVal[0][2] = "hello"; + sStruct.rgseqssFixVal[1][2] = "ohhello"; + sStruct.rgseqssFixVal[0][3] = "hallo"; + sStruct.rgseqssFixVal[1][3] = "ohhallo"; + sStruct.rgseqssFixVal[0][4] = "servus"; + sStruct.rgseqssFixVal[1][4] = "ohservus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.rgseq8Val[0].size(), 200); + EXPECT_EQ(sStruct.rgseq8Val[1].size(), 300); + for (size_t nIndex = 0; nIndex < sStruct.rgseq8Val[0].size() / 2; nIndex++) + EXPECT_EQ(sStruct.rgseq8Val[0][nIndex], sStruct.rgseq8Val[0][nIndex + sStruct.rgseq8Val[0].size() / 2]); + for (size_t nIndex = 0; nIndex < sStruct.rgseq8Val[1].size() / 3; nIndex++) + EXPECT_EQ(sStruct.rgseq8Val[1][nIndex], sStruct.rgseq8Val[1][nIndex + sStruct.rgseq8Val[1].size() / 3]); + EXPECT_EQ(sStruct.rgseqssVal[0].size(), 6); + EXPECT_EQ(sStruct.rgseqssVal[1].size(), 9); + for (size_t nIndex = 0; nIndex < sStruct.rgseqssVal[0].size() / 2; nIndex++) + EXPECT_EQ(sStruct.rgseqssVal[0][nIndex], sStruct.rgseqssVal[0][nIndex + sStruct.rgseqssVal[0].size() / 2]); + for (size_t nIndex = 0; nIndex < sStruct.rgseqssVal[1].size() / 3; nIndex++) + EXPECT_EQ(sStruct.rgseqssVal[1][nIndex], sStruct.rgseqssVal[1][nIndex + sStruct.rgseqssVal[1].size() / 3]); + EXPECT_EQ(sStruct.rgseq8FixVal[0].size(), 20); + EXPECT_EQ(sStruct.rgseq8FixVal[1].size(), 20); + for (size_t nIndex = 0; nIndex < sStruct.rgseq8FixVal[0].size(); nIndex++) + EXPECT_EQ(sStruct.rgseq8FixVal[0][nIndex], static_cast(nIndex << 1)); + for (size_t nIndex = 0; nIndex < sStruct.rgseq8FixVal[1].size(); nIndex++) + EXPECT_EQ(sStruct.rgseq8FixVal[1][nIndex], static_cast(nIndex << 1)); + EXPECT_EQ(sStruct.rgseqssFixVal[0].size(), 5); + EXPECT_EQ(sStruct.rgseqssFixVal[1].size(), 5); + EXPECT_EQ(sStruct.rgseqssFixVal[0][0], "hihi"); + EXPECT_EQ(sStruct.rgseqssFixVal[1][0], "ohhiohhi"); + EXPECT_EQ(sStruct.rgseqssFixVal[0][1], "hoihoi"); + EXPECT_EQ(sStruct.rgseqssFixVal[1][1], "ohhoiohhoi"); + EXPECT_EQ(sStruct.rgseqssFixVal[0][2], "hellohello"); + EXPECT_EQ(sStruct.rgseqssFixVal[1][2], "ohhelloohhello"); + EXPECT_EQ(sStruct.rgseqssFixVal[0][3], "hallohallo"); + EXPECT_EQ(sStruct.rgseqssFixVal[1][3], "ohhalloohhallo"); + EXPECT_EQ(sStruct.rgseqssFixVal[0][4], "servusservus"); + EXPECT_EQ(sStruct.rgseqssFixVal[1][4], "ohservusohservus"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaStructInterfaceArray) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + struct SMultiply : public IMultiplyValue + { + size_t Multiply(size_t n) + { + return n * n; + } + } sMultiply; + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + + //sStruct.rgidVal[0] = sdv::GetInterfaceId(); + //sStruct.rgidVal[1] = sdv::GetInterfaceId(); + //sStruct.rgifcVal[0] = static_cast(&sMultiply); + //sStruct.rgifcVal[1] = static_cast(&sMultiply); + //sStruct.rgpMultiplyValue[0] = &sMultiply; + //sStruct.rgpMultiplyValue[1] = &sMultiply; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + //EXPECT_EQ(sStruct.rgidVal[0], sdv::GetInterfaceId()); + //EXPECT_EQ(sStruct.rgidVal[1], sdv::GetInterfaceId()); + //EXPECT_EQ(sStruct.rgifcVal[0].id(), sdv::GetInterfaceId()); + //EXPECT_EQ(sStruct.rgifcVal[1].id(), sdv::GetInterfaceId()); + //ASSERT_NE(sStruct.rgifcVal[0], nullptr); + //ASSERT_NE(sStruct.rgifcVal[1], nullptr); + //ASSERT_NE(sStruct.rgifcVal[0].get(), nullptr); + //ASSERT_NE(sStruct.rgifcVal[1].get(), nullptr); + //EXPECT_EQ(sStruct.rgifcVal[0].get()->Add(99), 19602); // 99 * 99 + 99 * 99 + //EXPECT_EQ(sStruct.rgifcVal[1].get()->Add(99), 19602); // 99 * 99 + 99 * 99 + //ASSERT_NE(sStruct.rgpMultiplyValue[0], nullptr); + //ASSERT_NE(sStruct.rgpMultiplyValue[1], nullptr); + //EXPECT_EQ(sStruct.rgpMultiplyValue[0]->Multiply(5), 625); // 5 * 5 * 5 * 5 + //EXPECT_EQ(sStruct.rgpMultiplyValue[1]->Multiply(5), 625); // 5 * 5 * 5 * 5 + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaStructSubCompoundArray) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.rgsSubVal[0].i = 1; + sStruct.rgsSubVal[1].i = 2; + // Incompatible serdes generation: BUG #398509 + //sStruct.rgsIndVal[0].i = 10; + //sStruct.rgsIndVal[1].i = 20; + // Unnamed struct with array is not yet supported due to incorret serdes code generation: BUG #398246 + //sStruct.rgsUnnamedVal[0].i = 100; + //sStruct.rgsUnnamedVal[1].i = 200; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.rgsSubVal[0].i, 2); + EXPECT_EQ(sStruct.rgsSubVal[1].i, 4); + //EXPECT_EQ(sStruct.rgsIndVal[0].i, 20); + //EXPECT_EQ(sStruct.rgsIndVal[1].i, 40); + //EXPECT_EQ(sStruct.rgsUnnamedVal[0].i, 120); + //EXPECT_EQ(sStruct.rgsUnnamedVal[1].i, 240); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaStructBooleanTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.tbVal = false; + EXPECT_FALSE(sStruct.tbVal); + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_TRUE(sStruct.tbVal); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaStructIntegralTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.tsVal = -10; + sStruct.tusVal = 10; + sStruct.tlVal = -20; + sStruct.tulVal = 20; + sStruct.tllVal = -30; + sStruct.tullVal = 30; + sStruct.ti8Val = -40; + sStruct.tui8Val = 40; + sStruct.ti16Val = -50; + sStruct.tui16Val = 50; + sStruct.ti32Val = -60; + sStruct.tui32Val = 60; + sStruct.ti64Val = -70; + sStruct.tui64Val = 70; + sStruct.tnVal = 80; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.tsVal, -11); + EXPECT_EQ(sStruct.tusVal, 11u); + EXPECT_EQ(sStruct.tlVal, -21); + EXPECT_EQ(sStruct.tulVal, 21u); + EXPECT_EQ(sStruct.tllVal, -31); + EXPECT_EQ(sStruct.tullVal, 31u); + EXPECT_EQ(static_cast(sStruct.ti8Val), static_cast(-41)); // Cast needed for ARM compatibility + EXPECT_EQ(sStruct.tui8Val, 41u); + EXPECT_EQ(sStruct.ti16Val, -51); + EXPECT_EQ(sStruct.tui16Val, 51u); + EXPECT_EQ(sStruct.ti32Val, -61); + EXPECT_EQ(sStruct.tui32Val, 61u); + EXPECT_EQ(sStruct.ti64Val, -71); + EXPECT_EQ(sStruct.tui64Val, 71u); + EXPECT_EQ(sStruct.tnVal, 81); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaStructCharacterTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.tcVal = 'A'; + sStruct.tc16Val = u'D'; + sStruct.tc32Val = U'G'; + sStruct.twcVal = L'J'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.tcVal, 'B'); + EXPECT_EQ(sStruct.tc16Val, u'E'); + EXPECT_EQ(sStruct.tc32Val, U'H'); + EXPECT_EQ(sStruct.twcVal, L'K'); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaStructFloatingPointTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + float fVal = -1234.5678f; + sStruct.tfVal = fVal; + double dVal = 8765.4321; + sStruct.tdVal = dVal; + long double ldVal = -1234.4321; + sStruct.tldVal = ldVal; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.tfVal, fVal - 1000.000f); + EXPECT_EQ(sStruct.tdVal, dVal + 1000.000); + EXPECT_EQ(sStruct.tldVal, ldVal - 1000.000); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaStructEnumTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.teHelloVal = EHello::hello; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.teHelloVal, EHello::hallo); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaStructStringTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.tssVal = "hi"; + sStruct.tss8Val = "huhu"; + sStruct.tss16Val = u"hey"; + sStruct.tss32Val = U"hello"; + sStruct.twssVal = L"servus"; + sStruct.tssFixVal = "hi"; + sStruct.tss8FixVal = "huhu"; + sStruct.tss16FixVal = u"hey"; + sStruct.tss32FixVal = U"hello"; + sStruct.twssFixVal = L"servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.tssVal, "hij"); + EXPECT_EQ(sStruct.tss8Val, "huhuv"); + EXPECT_EQ(sStruct.tss16Val, u"heyz"); + EXPECT_EQ(sStruct.tss32Val, U"hellop"); + EXPECT_EQ(sStruct.twssVal, L"servust"); + EXPECT_EQ(sStruct.tssFixVal, "hij"); + EXPECT_EQ(sStruct.tss8FixVal, "huhuv"); + EXPECT_EQ(sStruct.tss16FixVal, u"heyz"); + EXPECT_EQ(sStruct.tss32FixVal, U"hellop"); + EXPECT_EQ(sStruct.twssFixVal, L"servust"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +// Using typedefs of template based types produced an error: BUG #398266 +TEST(IPC_Communication_Test_UDS_Tunnel, DISABLED_MarshallMegaStructPointerTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + //sStruct.tptr8Val.resize(100); + //for (size_t nIndex = 0; nIndex < sStruct.tptr8Val.size(); nIndex++) + // sStruct.tptr8Val[nIndex] = static_cast(nIndex); + //sStruct.tptrssVal.resize(3); + //sStruct.tptrssVal[0] = "hello"; + //sStruct.tptrssVal[1] = "huhu"; + //sStruct.tptrssVal[2] = "hi"; + //sStruct.tptr8FixVal.resize(sStruct.tptr8FixVal.capacity()); + //for (size_t nIndex = 0; nIndex < sStruct.tptr8FixVal.size(); nIndex++) + // sStruct.tptr8FixVal[nIndex] = static_cast(nIndex); + //sStruct.tptrssFixVal.resize(sStruct.tptrssFixVal.capacity()); + //sStruct.tptrssFixVal[0] = "hi"; + //sStruct.tptrssFixVal[1] = "hoi"; + //sStruct.tptrssFixVal[2] = "hello"; + //sStruct.tptrssFixVal[3] = "hallo"; + //sStruct.tptrssFixVal[4] = "servus"; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + //EXPECT_EQ(sStruct.tptr8Val.size(), 200); + //for (size_t nIndex = 0; nIndex < sStruct.tptr8Val.size() / 2; nIndex++) + // EXPECT_EQ(sStruct.tptr8Val[nIndex], sStruct.tptr8Val[nIndex + sStruct.tptr8Val.size() / 2]); + //EXPECT_EQ(sStruct.tptrssVal.size(), 6); + //for (size_t nIndex = 0; nIndex < sStruct.tptrssVal.size() / 2; nIndex++) + // EXPECT_EQ(sStruct.tptrssVal[nIndex], sStruct.tptrssVal[nIndex + sStruct.tptrssVal.size() / 2]); + //EXPECT_EQ(sStruct.tptr8FixVal.size(), 20); + //for (size_t nIndex = 0; nIndex < sStruct.tptr8FixVal.size(); nIndex++) + // EXPECT_EQ(sStruct.tptr8FixVal[nIndex], static_cast(nIndex << 1)); + //EXPECT_EQ(sStruct.tptrssFixVal.size(), 5); + //EXPECT_EQ(sStruct.tptrssFixVal[0], "hihi"); + //EXPECT_EQ(sStruct.tptrssFixVal[1], "hoihoi"); + //EXPECT_EQ(sStruct.tptrssFixVal[2], "hellohello"); + //EXPECT_EQ(sStruct.tptrssFixVal[3], "hallohallo"); + //EXPECT_EQ(sStruct.tptrssFixVal[4], "servusservus"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +// Using typedefs of template based types produced an error: BUG #398266 +TEST(IPC_Communication_Test_UDS_Tunnel, DISABLED_MarshallMegaStructSequenceTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + //sStruct.tseq8Val.resize(100); + //for (size_t nIndex = 0; nIndex < sStruct.tseq8Val.size(); nIndex++) + // sStruct.tseq8Val[nIndex] = static_cast(nIndex); + //sStruct.tseqssVal.resize(3); + //sStruct.tseqssVal[0] = "hello"; + //sStruct.tseqssVal[1] = "huhu"; + //sStruct.tseqssVal[2] = "hi"; + //sStruct.tseq8FixVal.resize(sStruct.tseq8FixVal.capacity()); + //for (size_t nIndex = 0; nIndex < sStruct.tseq8FixVal.size(); nIndex++) + // sStruct.tseq8FixVal[nIndex] = static_cast(nIndex); + //sStruct.tseqssFixVal.resize(sStruct.tseqssFixVal.capacity()); + //sStruct.tseqssFixVal[0] = "hi"; + //sStruct.tseqssFixVal[1] = "hoi"; + //sStruct.tseqssFixVal[2] = "hello"; + //sStruct.tseqssFixVal[3] = "hallo"; + //sStruct.tseqssFixVal[4] = "servus"; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + //EXPECT_EQ(sStruct.tseq8Val.size(), 200); + //for (size_t nIndex = 0; nIndex < sStruct.tseq8Val.size() / 2; nIndex++) + // EXPECT_EQ(sStruct.tseq8Val[nIndex], sStruct.tseq8Val[nIndex + sStruct.tseq8Val.size() / 2]); + //EXPECT_EQ(sStruct.tseqssVal.size(), 6); + //for (size_t nIndex = 0; nIndex < sStruct.tseqssVal.size() / 2; nIndex++) + // EXPECT_EQ(sStruct.tseqssVal[nIndex], sStruct.tseqssVal[nIndex + sStruct.tseqssVal.size() / 2]); + //EXPECT_EQ(sStruct.tseq8FixVal.size(), 20); + //for (size_t nIndex = 0; nIndex < sStruct.tseq8FixVal.size(); nIndex++) + // EXPECT_EQ(sStruct.tseq8FixVal[nIndex], static_cast(nIndex << 1)); + //EXPECT_EQ(sStruct.tseqssFixVal.size(), 5); + //EXPECT_EQ(sStruct.tseqssFixVal[0], "hihi"); + //EXPECT_EQ(sStruct.tseqssFixVal[1], "hoihoi"); + //EXPECT_EQ(sStruct.tseqssFixVal[2], "hellohello"); + //EXPECT_EQ(sStruct.tseqssFixVal[3], "hallohallo"); + //EXPECT_EQ(sStruct.tseqssFixVal[4], "servusservus"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaStructInterfaceTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + struct SMultiply : public IMultiplyValue + { + size_t Multiply(size_t n) + { + return n * n; + } + } sMultiply; + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + + sStruct.tidVal = sdv::GetInterfaceId(); + sStruct.tifcVal = static_cast(&sMultiply); + // Typedef of interfaces current not possible. BUG #399464 + //sStruct.ptMultiplyValue = &sMultiply; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.tidVal, sdv::GetInterfaceId()); + EXPECT_EQ(sStruct.tifcVal.id(), sdv::GetInterfaceId()); + ASSERT_NE(sStruct.tifcVal, nullptr); + ASSERT_NE(sStruct.tifcVal.get(), nullptr); + EXPECT_EQ(sStruct.tifcVal.get()->Add(99), 19602); // 99 * 99 + 99 * 99 + // Typedef of interfaces current not possible. BUG #399464 + //ASSERT_NE(sStruct.tpMultiplyValue, nullptr); + //EXPECT_EQ(sStruct.tpMultiplyValue->Multiply(5), 625); // 5 * 5 * 5 * 5 + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaStructSubCompoundTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaStruct sStruct{}; + sStruct.tsSubVal.i = 1; + sStruct.tsIndVal.i = 10; + // Incompatible serdes generation: BUG #398509 + //sStruct.tsUnnamedVal.i = 100; + EXPECT_NO_THROW(pMegaTest->ProcessMegaStruct(sStruct)); + EXPECT_EQ(sStruct.tsSubVal.i, 2); + EXPECT_EQ(sStruct.tsIndVal.i, 20); + // Incompatible serdes generation: BUG #398509 + //EXPECT_EQ(sStruct.tsUnnamedVal.i, 120); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaTypeBasedUnionBoolean) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(1); + uUnion.bVal = false; + EXPECT_FALSE(uUnion.bVal); + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_TRUE(uUnion.bVal); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaTypeBasedUnionIntegral) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(2); + uUnion.sVal = -10; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.sVal, -11); + uUnion.switch_to(3); + uUnion.usVal = 10; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.usVal, 11u); + uUnion.switch_to(4); + uUnion.lVal = -20; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.lVal, -21); + uUnion.switch_to(5); + uUnion.ulVal = 20; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ulVal, 21u); + uUnion.switch_to(6); + uUnion.llVal = -30; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.llVal, -31); + uUnion.switch_to(7); + uUnion.ullVal = 30; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ullVal, 31u); + uUnion.switch_to(8); + uUnion.i8Val = -40; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(static_cast(uUnion.i8Val), static_cast(-41)); // Cast needed for ARM compatibility + uUnion.switch_to(9); + uUnion.ui8Val = 40; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ui8Val, 41u); + uUnion.switch_to(10); + uUnion.i16Val = -50; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.i16Val, -51); + uUnion.switch_to(11); + uUnion.ui16Val = 50; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ui16Val, 51u); + uUnion.switch_to(12); + uUnion.i32Val = -60; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.i32Val, -61); + uUnion.switch_to(13); + uUnion.ui32Val = 60; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ui32Val, 61u); + uUnion.switch_to(14); + uUnion.i64Val = -70; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.i64Val, -71); + uUnion.switch_to(15); + uUnion.ui64Val = 70; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ui64Val, 71u); + uUnion.switch_to(23); + uUnion.nVal = 80; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.nVal, 81); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaTypeBasedUnionCharacter) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(16); + uUnion.cVal = 'A'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.cVal, 'B'); + uUnion.switch_to(17); + uUnion.c16Val = u'D'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.c16Val, u'E'); + uUnion.switch_to(18); + uUnion.c32Val = U'G'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.c32Val, U'H'); + uUnion.switch_to(19); + uUnion.wcVal = L'J'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.wcVal, L'K'); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaTypeBasedUnionFloatingPoint) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(20); + float fVal = -1234.5678f; + uUnion.fVal = fVal; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.fVal, fVal - 1000.000f); + uUnion.switch_to(21); + double dVal = 8765.4321; + uUnion.dVal = dVal; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.dVal, dVal + 1000.000); + // GCC issue with generated initialization in the consructor for the "long double" type: BUG #3982727 + //uUnion.switch_to(22); + //long double ldVal = -1234.4321; + //uUnion.ldVal = ldVal; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + //EXPECT_EQ(uUnion.ldVal, ldVal - 1000.000); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaTypeBasedUnionEnum) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(24); + uUnion.eHelloVal = EHello::hello; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.eHelloVal, EHello::hallo); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaTypeBasedUnionString) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(26); + uUnion.ssVal = "hi"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ssVal, "hij"); + uUnion.switch_to(27); + uUnion.ss8Val = "huhu"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ss8Val, "huhuv"); + uUnion.switch_to(28); + uUnion.ss16Val = u"hey"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ss16Val, u"heyz"); + uUnion.switch_to(29); + uUnion.ss32Val = U"hello"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ss32Val, U"hellop"); + uUnion.switch_to(30); + uUnion.wssVal = L"servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.wssVal, L"servust"); + uUnion.switch_to(31); + uUnion.ssFixVal = "hi"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ssFixVal, "hij"); + uUnion.switch_to(32); + uUnion.ss8FixVal = "huhu"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ss8FixVal, "huhuv"); + uUnion.switch_to(33); + uUnion.ss16FixVal = u"hey"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ss16FixVal, u"heyz"); + uUnion.switch_to(34); + uUnion.ss32FixVal = U"hello"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ss32FixVal, U"hellop"); + uUnion.switch_to(35); + uUnion.wssFixVal = L"servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.wssFixVal, L"servust"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaTypeBasedUnionPointer) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(36); + uUnion.ptr8Val.resize(100); + for (size_t nIndex = 0; nIndex < uUnion.ptr8Val.size(); nIndex++) + uUnion.ptr8Val[nIndex] = static_cast(nIndex); + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ptr8Val.size(), 200); + for (size_t nIndex = 0; nIndex < uUnion.ptr8Val.size() / 2; nIndex++) + EXPECT_EQ(uUnion.ptr8Val[nIndex], uUnion.ptr8Val[nIndex + uUnion.ptr8Val.size() / 2]); + uUnion.switch_to(37); + uUnion.ptrssVal.resize(3); + uUnion.ptrssVal[0] = "hello"; + uUnion.ptrssVal[1] = "huhu"; + uUnion.ptrssVal[2] = "hi"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ptrssVal.size(), 6); + for (size_t nIndex = 0; nIndex < uUnion.ptrssVal.size() / 2; nIndex++) + EXPECT_EQ(uUnion.ptrssVal[nIndex], uUnion.ptrssVal[nIndex + uUnion.ptrssVal.size() / 2]); + uUnion.switch_to(38); + uUnion.ptr8FixVal.resize(uUnion.ptr8FixVal.capacity()); + for (size_t nIndex = 0; nIndex < uUnion.ptr8FixVal.size(); nIndex++) + uUnion.ptr8FixVal[nIndex] = static_cast(nIndex); + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ptr8FixVal.size(), 20); + for (size_t nIndex = 0; nIndex < uUnion.ptr8FixVal.size(); nIndex++) + EXPECT_EQ(uUnion.ptr8FixVal[nIndex], static_cast(nIndex << 1)); + uUnion.switch_to(39); + uUnion.ptrssFixVal.resize(uUnion.ptrssFixVal.capacity()); + uUnion.ptrssFixVal[0] = "hi"; + uUnion.ptrssFixVal[1] = "hoi"; + uUnion.ptrssFixVal[2] = "hello"; + uUnion.ptrssFixVal[3] = "hallo"; + uUnion.ptrssFixVal[4] = "servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ptrssFixVal.size(), 5); + EXPECT_EQ(uUnion.ptrssFixVal[0], "hihi"); + EXPECT_EQ(uUnion.ptrssFixVal[1], "hoihoi"); + EXPECT_EQ(uUnion.ptrssFixVal[2], "hellohello"); + EXPECT_EQ(uUnion.ptrssFixVal[3], "hallohallo"); + EXPECT_EQ(uUnion.ptrssFixVal[4], "servusservus"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaTypeBasedUnionSequence) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(36); + uUnion.seq8Val.resize(100); + for (size_t nIndex = 0; nIndex < uUnion.seq8Val.size(); nIndex++) + uUnion.seq8Val[nIndex] = static_cast(nIndex); + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.seq8Val.size(), 200); + for (size_t nIndex = 0; nIndex < uUnion.seq8Val.size() / 2; nIndex++) + EXPECT_EQ(uUnion.seq8Val[nIndex], uUnion.seq8Val[nIndex + uUnion.seq8Val.size() / 2]); + uUnion.switch_to(37); + uUnion.seqssVal.resize(3); + uUnion.seqssVal[0] = "hello"; + uUnion.seqssVal[1] = "huhu"; + uUnion.seqssVal[2] = "hi"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.seqssVal.size(), 6); + for (size_t nIndex = 0; nIndex < uUnion.seqssVal.size() / 2; nIndex++) + EXPECT_EQ(uUnion.seqssVal[nIndex], uUnion.seqssVal[nIndex + uUnion.seqssVal.size() / 2]); + uUnion.switch_to(38); + uUnion.seq8FixVal.resize(uUnion.seq8FixVal.capacity()); + for (size_t nIndex = 0; nIndex < uUnion.seq8FixVal.size(); nIndex++) + uUnion.seq8FixVal[nIndex] = static_cast(nIndex); + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.seq8FixVal.size(), 20); + for (size_t nIndex = 0; nIndex < uUnion.seq8FixVal.size(); nIndex++) + EXPECT_EQ(uUnion.seq8FixVal[nIndex], static_cast(nIndex << 1)); + uUnion.switch_to(39); + uUnion.seqssFixVal.resize(uUnion.seqssFixVal.capacity()); + uUnion.seqssFixVal[0] = "hi"; + uUnion.seqssFixVal[1] = "hoi"; + uUnion.seqssFixVal[2] = "hello"; + uUnion.seqssFixVal[3] = "hallo"; + uUnion.seqssFixVal[4] = "servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.seqssFixVal.size(), 5); + EXPECT_EQ(uUnion.seqssFixVal[0], "hihi"); + EXPECT_EQ(uUnion.seqssFixVal[1], "hoihoi"); + EXPECT_EQ(uUnion.seqssFixVal[2], "hellohello"); + EXPECT_EQ(uUnion.seqssFixVal[3], "hallohallo"); + EXPECT_EQ(uUnion.seqssFixVal[4], "servusservus"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaTypeBasedUnionInterface) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + struct SMultiply : public IMultiplyValue + { + size_t Multiply(size_t n) + { + return n * n; + } + } sMultiply; + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(25); + + uUnion.idVal = sdv::GetInterfaceId(); + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.idVal, sdv::GetInterfaceId()); + uUnion.switch_to(44); + uUnion.ifcVal = static_cast(&sMultiply); + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ifcVal.id(), sdv::GetInterfaceId()); + ASSERT_NE(uUnion.ifcVal, nullptr); + ASSERT_NE(uUnion.ifcVal.get(), nullptr); + EXPECT_EQ(uUnion.ifcVal.get()->Add(99), 19602); // 99 * 99 + 99 * 99 + uUnion.switch_to(45); + uUnion.pMultiplyValue = &sMultiply; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + ASSERT_NE(uUnion.pMultiplyValue, nullptr); + EXPECT_EQ(uUnion.pMultiplyValue->Multiply(5), 625); // 5 * 5 * 5 * 5 + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaTypeBasedUnionSubCompound) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(46); + uUnion.sSubVal.i = 1; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.sSubVal.i, 2); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaTypeBasedUnionBooleanTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(201); + uUnion.tbVal = false; + EXPECT_FALSE(uUnion.tbVal); + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_TRUE(uUnion.tbVal); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaTypeBasedUnionIntegralTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(202); + uUnion.tsVal = -10; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tsVal, -11); + uUnion.switch_to(203); + uUnion.tusVal = 10; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tusVal, 11u); + uUnion.switch_to(204); + uUnion.tlVal = -20; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tlVal, -21); + uUnion.switch_to(205); + uUnion.tulVal = 20; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tulVal, 21u); + uUnion.switch_to(206); + uUnion.tllVal = -30; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tllVal, -31); + uUnion.switch_to(207); + uUnion.tullVal = 30; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tullVal, 31u); + uUnion.switch_to(208); + uUnion.ti8Val = -40; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(static_cast(uUnion.ti8Val), static_cast(-41)); // Cast needed for ARM compatibility + uUnion.switch_to(209); + uUnion.tui8Val = 40; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tui8Val, 41u); + uUnion.switch_to(210); + uUnion.ti16Val = -50; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ti16Val, -51); + uUnion.switch_to(211); + uUnion.tui16Val = 50; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tui16Val, 51u); + uUnion.switch_to(212); + uUnion.ti32Val = -60; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ti32Val, -61); + uUnion.switch_to(213); + uUnion.tui32Val = 60; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tui32Val, 61u); + uUnion.switch_to(214); + uUnion.ti64Val = -70; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.ti64Val, -71); + uUnion.switch_to(215); + uUnion.tui64Val = 70; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tui64Val, 71u); + uUnion.switch_to(223); + uUnion.tnVal = 80; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tnVal, 81); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaTypeBasedUnionCharacterTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(216); + uUnion.tcVal = 'A'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tcVal, 'B'); + uUnion.switch_to(217); + uUnion.tc16Val = u'D'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tc16Val, u'E'); + uUnion.switch_to(218); + uUnion.tc32Val = U'G'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tc32Val, U'H'); + uUnion.switch_to(219); + uUnion.twcVal = L'J'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.twcVal, L'K'); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaTypeBasedUnionFloatingPointTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(220); + float fVal = -1234.5678f; + uUnion.tfVal = fVal; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tfVal, fVal - 1000.000f); + uUnion.switch_to(221); + double dVal = 8765.4321; + uUnion.tdVal = dVal; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tdVal, dVal + 1000.000); + // GCC issue with generated initialization in the consructor for the "long double" type: BUG #3982727 + //uUnion.switch_to(222); + //long double ldVal = -1234.4321; + //uUnion.tldVal = ldVal; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + //EXPECT_EQ(uUnion.tldVal, ldVal - 1000.000); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaTypeBasedUnionEnumTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(224); + uUnion.teHelloVal = EHello::hello; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.teHelloVal, EHello::hallo); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaTypeBasedUnionStringTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(226); + uUnion.tssVal = "hi"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tssVal, "hij"); + uUnion.switch_to(227); + uUnion.tss8Val = "huhu"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tss8Val, "huhuv"); + uUnion.switch_to(228); + uUnion.tss16Val = u"hey"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tss16Val, u"heyz"); + uUnion.switch_to(229); + uUnion.tss32Val = U"hello"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tss32Val, U"hellop"); + uUnion.switch_to(230); + uUnion.twssVal = L"servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.twssVal, L"servust"); + uUnion.switch_to(231); + uUnion.tssFixVal = "hi"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tssFixVal, "hij"); + uUnion.switch_to(232); + uUnion.tss8FixVal = "huhu"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tss8FixVal, "huhuv"); + uUnion.switch_to(233); + uUnion.tss16FixVal = u"hey"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tss16FixVal, u"heyz"); + uUnion.switch_to(234); + uUnion.tss32FixVal = U"hello"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tss32FixVal, U"hellop"); + uUnion.switch_to(235); + uUnion.twssFixVal = L"servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.twssFixVal, L"servust"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +// Using typedefs of template based types produced an error: BUG #398266 +TEST(IPC_Communication_Test_UDS_Tunnel, DISABLED_MarshallMegaTypeBasedUnionPointerTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(236); + //uUnion.tptr8Val.resize(100); + //for (size_t nIndex = 0; nIndex < uUnion.tptr8Val.size(); nIndex++) + // uUnion.tptr8Val[nIndex] = static_cast(nIndex); + //EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + //EXPECT_EQ(uUnion.tptr8Val.size(), 200); + //for (size_t nIndex = 0; nIndex < uUnion.tptr8Val.size() / 2; nIndex++) + // EXPECT_EQ(uUnion.tptr8Val[nIndex], uUnion.tptr8Val[nIndex + uUnion.tptr8Val.size() / 2]); + //uUnion.switch_to(237); + //uUnion.tptrssVal.resize(3); + //uUnion.tptrssVal[0] = "hello"; + //uUnion.tptrssVal[1] = "huhu"; + //uUnion.tptrssVal[2] = "hi"; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + //EXPECT_EQ(uUnion.tptrssVal.size(), 6); + //for (size_t nIndex = 0; nIndex < uUnion.tptrssVal.size() / 2; nIndex++) + // EXPECT_EQ(uUnion.tptrssVal[nIndex], uUnion.tptrssVal[nIndex + uUnion.tptrssVal.size() / 2]); + //uUnion.switch_to(238); + //uUnion.tptr8FixVal.resize(uUnion.tptr8FixVal.capacity()); + //for (size_t nIndex = 0; nIndex < uUnion.tptr8FixVal.size(); nIndex++) + // uUnion.tptr8FixVal[nIndex] = static_cast(nIndex); + //EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + //EXPECT_EQ(uUnion.tptr8FixVal.size(), 20); + //for (size_t nIndex = 0; nIndex < uUnion.tptr8FixVal.size(); nIndex++) + // EXPECT_EQ(uUnion.tptr8FixVal[nIndex], static_cast(nIndex << 1)); + //uUnion.switch_to(239); + //uUnion.tptrssFixVal.resize(uUnion.tptrssFixVal.capacity()); + //uUnion.tptrssFixVal[0] = "hi"; + //uUnion.tptrssFixVal[1] = "hoi"; + //uUnion.tptrssFixVal[2] = "hello"; + //uUnion.tptrssFixVal[3] = "hallo"; + //uUnion.tptrssFixVal[4] = "servus"; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + //EXPECT_EQ(uUnion.tptrssFixVal.size(), 5); + //EXPECT_EQ(uUnion.tptrssFixVal[0], "hihi"); + //EXPECT_EQ(uUnion.tptrssFixVal[1], "hoihoi"); + //EXPECT_EQ(uUnion.tptrssFixVal[2], "hellohello"); + //EXPECT_EQ(uUnion.tptrssFixVal[3], "hallohallo"); + //EXPECT_EQ(uUnion.tptrssFixVal[4], "servusservus"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +// Using typedefs of template based types produced an error: BUG #398266 +TEST(IPC_Communication_Test_UDS_Tunnel, DISABLED_MarshallMegaTypeBasedUnionSequenceTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(236); + //uUnion.tseq8Val.resize(100); + //for (size_t nIndex = 0; nIndex < uUnion.tseq8Val.size(); nIndex++) + // uUnion.tseq8Val[nIndex] = static_cast(nIndex); + //EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + //EXPECT_EQ(uUnion.tseq8Val.size(), 200); + //for (size_t nIndex = 0; nIndex < uUnion.tseq8Val.size() / 2; nIndex++) + // EXPECT_EQ(uUnion.tseq8Val[nIndex], uUnion.tseq8Val[nIndex + uUnion.tseq8Val.size() / 2]); + //uUnion.switch_to(237); + //uUnion.tseqssVal.resize(3); + //uUnion.tseqssVal[0] = "hello"; + //uUnion.tseqssVal[1] = "huhu"; + //uUnion.tseqssVal[2] = "hi"; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + //EXPECT_EQ(uUnion.tseqssVal.size(), 6); + //for (size_t nIndex = 0; nIndex < uUnion.tseqssVal.size() / 2; nIndex++) + // EXPECT_EQ(uUnion.tseqssVal[nIndex], uUnion.tseqssVal[nIndex + uUnion.tseqssVal.size() / 2]); + //uUnion.switch_to(238); + //uUnion.tseq8FixVal.resize(uUnion.tseq8FixVal.capacity()); + //for (size_t nIndex = 0; nIndex < uUnion.tseq8FixVal.size(); nIndex++) + // uUnion.tseq8FixVal[nIndex] = static_cast(nIndex); + //EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + //EXPECT_EQ(uUnion.tseq8FixVal.size(), 20); + //for (size_t nIndex = 0; nIndex < uUnion.tseq8FixVal.size(); nIndex++) + // EXPECT_EQ(uUnion.tseq8FixVal[nIndex], static_cast(nIndex << 1)); + //uUnion.switch_to(239); + //uUnion.tseqssFixVal.resize(uUnion.tseqssFixVal.capacity()); + //uUnion.tseqssFixVal[0] = "hi"; + //uUnion.tseqssFixVal[1] = "hoi"; + //uUnion.tseqssFixVal[2] = "hello"; + //uUnion.tseqssFixVal[3] = "hallo"; + //uUnion.tseqssFixVal[4] = "servus"; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + //EXPECT_EQ(uUnion.tseqssFixVal.size(), 5); + //EXPECT_EQ(uUnion.tseqssFixVal[0], "hihi"); + //EXPECT_EQ(uUnion.tseqssFixVal[1], "hoihoi"); + //EXPECT_EQ(uUnion.tseqssFixVal[2], "hellohello"); + //EXPECT_EQ(uUnion.tseqssFixVal[3], "hallohallo"); + //EXPECT_EQ(uUnion.tseqssFixVal[4], "servusservus"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaTypeBasedUnionInterfaceTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + struct SMultiply : public IMultiplyValue + { + size_t Multiply(size_t n) + { + return n * n; + } + } sMultiply; + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(225); + + uUnion.tidVal = sdv::GetInterfaceId(); + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tidVal, sdv::GetInterfaceId()); + uUnion.switch_to(244); + uUnion.tifcVal = static_cast(&sMultiply); + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tifcVal.id(), sdv::GetInterfaceId()); + ASSERT_NE(uUnion.tifcVal, nullptr); + ASSERT_NE(uUnion.tifcVal.get(), nullptr); + EXPECT_EQ(uUnion.tifcVal.get()->Add(99), 19602); // 99 * 99 + 99 * 99 + // Typedef of interfaces current not possible. BUG #399464 + //uUnion.switch_to(245); + //uUnion.tpMultiplyValue = &sMultiply; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + //ASSERT_NE(uUnion.tpMultiplyValue, nullptr); + //EXPECT_EQ(uUnion.tpMultiplyValue->Multiply(5), 625); // 5 * 5 * 5 * 5 + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaTypeBasedUnionSubCompoundTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + UMegaTypeBasedUnion uUnion; + uUnion.switch_to(246); + uUnion.tsSubVal.i = 1; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tsSubVal.i, 2); + uUnion.switch_to(247); + uUnion.tsIndVal.i = 10; + EXPECT_NO_THROW(pMegaTest->ProcessMegaTypeBasedUnion(uUnion)); + EXPECT_EQ(uUnion.tsIndVal.i, 20); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaVarBasedUnionBoolean) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(1); + sUnion.uVal.bVal = false; + EXPECT_FALSE(sUnion.uVal.bVal); + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_TRUE(sUnion.uVal.bVal); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaVarBasedUnionIntegral) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(2); + sUnion.uVal.sVal = -10; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.sVal, -11); + sUnion.switch_to(3); + sUnion.uVal.usVal = 10; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.usVal, 11u); + sUnion.switch_to(4); + sUnion.uVal.lVal = -20; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.lVal, -21); + sUnion.switch_to(5); + sUnion.uVal.ulVal = 20; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ulVal, 21u); + sUnion.switch_to(6); + sUnion.uVal.llVal = -30; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.llVal, -31); + sUnion.switch_to(7); + sUnion.uVal.ullVal = 30; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ullVal, 31u); + sUnion.switch_to(8); + sUnion.uVal.i8Val = -40; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(static_cast(sUnion.uVal.i8Val), static_cast(-41)); // Cast needed for ARM compatibility + sUnion.switch_to(9); + sUnion.uVal.ui8Val = 40; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ui8Val, 41u); + sUnion.switch_to(10); + sUnion.uVal.i16Val = -50; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.i16Val, -51); + sUnion.switch_to(11); + sUnion.uVal.ui16Val = 50; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ui16Val, 51u); + sUnion.switch_to(12); + sUnion.uVal.i32Val = -60; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.i32Val, -61); + sUnion.switch_to(13); + sUnion.uVal.ui32Val = 60; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ui32Val, 61u); + sUnion.switch_to(14); + sUnion.uVal.i64Val = -70; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.i64Val, -71); + sUnion.switch_to(15); + sUnion.uVal.ui64Val = 70; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ui64Val, 71u); + sUnion.switch_to(23); + sUnion.uVal.nVal = 80; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.nVal, 81); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaVarBasedUnionCharacter) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(16); + sUnion.uVal.cVal = 'A'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.cVal, 'B'); + sUnion.switch_to(17); + sUnion.uVal.c16Val = u'D'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.c16Val, u'E'); + sUnion.switch_to(18); + sUnion.uVal.c32Val = U'G'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.c32Val, U'H'); + sUnion.switch_to(19); + sUnion.uVal.wcVal = L'J'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.wcVal, L'K'); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaVarBasedUnionFloatingPoint) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(20); + float fVal = -1234.5678f; + sUnion.uVal.fVal = fVal; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.fVal, fVal - 1000.000f); + sUnion.switch_to(21); + double dVal = 8765.4321; + sUnion.uVal.dVal = dVal; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.dVal, dVal + 1000.000); + // GCC issue with generated initialization in the consructor for the "long double" type: BUG #3982727 + //sUnion.switch_to(22); + //long double ldVal = -1234.4321; + //sUnion.uVal.ldVal = ldVal; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + //EXPECT_EQ(sUnion.uVal.ldVal, ldVal - 1000.000); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaVarBasedUnionEnum) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(24); + sUnion.uVal.eHelloVal = EHello::hello; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.eHelloVal, EHello::hallo); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaVarBasedUnionString) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(26); + sUnion.uVal.ssVal = "hi"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ssVal, "hij"); + sUnion.switch_to(27); + sUnion.uVal.ss8Val = "huhu"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ss8Val, "huhuv"); + sUnion.switch_to(28); + sUnion.uVal.ss16Val = u"hey"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ss16Val, u"heyz"); + sUnion.switch_to(29); + sUnion.uVal.ss32Val = U"hello"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ss32Val, U"hellop"); + sUnion.switch_to(30); + sUnion.uVal.wssVal = L"servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.wssVal, L"servust"); + sUnion.switch_to(31); + sUnion.uVal.ssFixVal = "hi"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ssFixVal, "hij"); + sUnion.switch_to(32); + sUnion.uVal.ss8FixVal = "huhu"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ss8FixVal, "huhuv"); + sUnion.switch_to(33); + sUnion.uVal.ss16FixVal = u"hey"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ss16FixVal, u"heyz"); + sUnion.switch_to(34); + sUnion.uVal.ss32FixVal = U"hello"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ss32FixVal, U"hellop"); + sUnion.switch_to(35); + sUnion.uVal.wssFixVal = L"servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.wssFixVal, L"servust"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaVarBasedUnionPointer) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(36); + sUnion.uVal.ptr8Val.resize(100); + for (size_t nIndex = 0; nIndex < sUnion.uVal.ptr8Val.size(); nIndex++) + sUnion.uVal.ptr8Val[nIndex] = static_cast(nIndex); + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ptr8Val.size(), 200); + for (size_t nIndex = 0; nIndex < sUnion.uVal.ptr8Val.size() / 2; nIndex++) + EXPECT_EQ(sUnion.uVal.ptr8Val[nIndex], sUnion.uVal.ptr8Val[nIndex + sUnion.uVal.ptr8Val.size() / 2]); + sUnion.switch_to(37); + sUnion.uVal.ptrssVal.resize(3); + sUnion.uVal.ptrssVal[0] = "hello"; + sUnion.uVal.ptrssVal[1] = "huhu"; + sUnion.uVal.ptrssVal[2] = "hi"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ptrssVal.size(), 6); + for (size_t nIndex = 0; nIndex < sUnion.uVal.ptrssVal.size() / 2; nIndex++) + EXPECT_EQ(sUnion.uVal.ptrssVal[nIndex], sUnion.uVal.ptrssVal[nIndex + sUnion.uVal.ptrssVal.size() / 2]); + sUnion.switch_to(38); + sUnion.uVal.ptr8FixVal.resize(sUnion.uVal.ptr8FixVal.capacity()); + for (size_t nIndex = 0; nIndex < sUnion.uVal.ptr8FixVal.size(); nIndex++) + sUnion.uVal.ptr8FixVal[nIndex] = static_cast(nIndex); + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ptr8FixVal.size(), 20); + for (size_t nIndex = 0; nIndex < sUnion.uVal.ptr8FixVal.size(); nIndex++) + EXPECT_EQ(sUnion.uVal.ptr8FixVal[nIndex], static_cast(nIndex << 1)); + sUnion.switch_to(39); + sUnion.uVal.ptrssFixVal.resize(sUnion.uVal.ptrssFixVal.capacity()); + sUnion.uVal.ptrssFixVal[0] = "hi"; + sUnion.uVal.ptrssFixVal[1] = "hoi"; + sUnion.uVal.ptrssFixVal[2] = "hello"; + sUnion.uVal.ptrssFixVal[3] = "hallo"; + sUnion.uVal.ptrssFixVal[4] = "servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ptrssFixVal.size(), 5); + EXPECT_EQ(sUnion.uVal.ptrssFixVal[0], "hihi"); + EXPECT_EQ(sUnion.uVal.ptrssFixVal[1], "hoihoi"); + EXPECT_EQ(sUnion.uVal.ptrssFixVal[2], "hellohello"); + EXPECT_EQ(sUnion.uVal.ptrssFixVal[3], "hallohallo"); + EXPECT_EQ(sUnion.uVal.ptrssFixVal[4], "servusservus"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaVarBasedUnionSequence) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(36); + sUnion.uVal.seq8Val.resize(100); + for (size_t nIndex = 0; nIndex < sUnion.uVal.seq8Val.size(); nIndex++) + sUnion.uVal.seq8Val[nIndex] = static_cast(nIndex); + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.seq8Val.size(), 200); + for (size_t nIndex = 0; nIndex < sUnion.uVal.seq8Val.size() / 2; nIndex++) + EXPECT_EQ(sUnion.uVal.seq8Val[nIndex], sUnion.uVal.seq8Val[nIndex + sUnion.uVal.seq8Val.size() / 2]); + sUnion.switch_to(37); + sUnion.uVal.seqssVal.resize(3); + sUnion.uVal.seqssVal[0] = "hello"; + sUnion.uVal.seqssVal[1] = "huhu"; + sUnion.uVal.seqssVal[2] = "hi"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.seqssVal.size(), 6); + for (size_t nIndex = 0; nIndex < sUnion.uVal.seqssVal.size() / 2; nIndex++) + EXPECT_EQ(sUnion.uVal.seqssVal[nIndex], sUnion.uVal.seqssVal[nIndex + sUnion.uVal.seqssVal.size() / 2]); + sUnion.switch_to(38); + sUnion.uVal.seq8FixVal.resize(sUnion.uVal.seq8FixVal.capacity()); + for (size_t nIndex = 0; nIndex < sUnion.uVal.seq8FixVal.size(); nIndex++) + sUnion.uVal.seq8FixVal[nIndex] = static_cast(nIndex); + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.seq8FixVal.size(), 20); + for (size_t nIndex = 0; nIndex < sUnion.uVal.seq8FixVal.size(); nIndex++) + EXPECT_EQ(sUnion.uVal.seq8FixVal[nIndex], static_cast(nIndex << 1)); + sUnion.switch_to(39); + sUnion.uVal.seqssFixVal.resize(sUnion.uVal.seqssFixVal.capacity()); + sUnion.uVal.seqssFixVal[0] = "hi"; + sUnion.uVal.seqssFixVal[1] = "hoi"; + sUnion.uVal.seqssFixVal[2] = "hello"; + sUnion.uVal.seqssFixVal[3] = "hallo"; + sUnion.uVal.seqssFixVal[4] = "servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.seqssFixVal.size(), 5); + EXPECT_EQ(sUnion.uVal.seqssFixVal[0], "hihi"); + EXPECT_EQ(sUnion.uVal.seqssFixVal[1], "hoihoi"); + EXPECT_EQ(sUnion.uVal.seqssFixVal[2], "hellohello"); + EXPECT_EQ(sUnion.uVal.seqssFixVal[3], "hallohallo"); + EXPECT_EQ(sUnion.uVal.seqssFixVal[4], "servusservus"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaVarBasedUnionInterface) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + struct SMultiply : public IMultiplyValue + { + size_t Multiply(size_t n) + { + return n * n; + } + } sMultiply; + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(25); + + sUnion.uVal.idVal = sdv::GetInterfaceId(); + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.idVal, sdv::GetInterfaceId()); + sUnion.switch_to(44); + sUnion.uVal.ifcVal = static_cast(&sMultiply); + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ifcVal.id(), sdv::GetInterfaceId()); + ASSERT_NE(sUnion.uVal.ifcVal, nullptr); + ASSERT_NE(sUnion.uVal.ifcVal.get(), nullptr); + EXPECT_EQ(sUnion.uVal.ifcVal.get()->Add(99), 19602); // 99 * 99 + 99 * 99 + sUnion.switch_to(45); + sUnion.uVal.pMultiplyValue = &sMultiply; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + ASSERT_NE(sUnion.uVal.pMultiplyValue, nullptr); + EXPECT_EQ(sUnion.uVal.pMultiplyValue->Multiply(5), 625); // 5 * 5 * 5 * 5 + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaVarBasedUnionSubCompound) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(46); + sUnion.uVal.sSubVal.i = 1; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.sSubVal.i, 2); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaVarBasedUnionBooleanTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(201); + sUnion.uVal.tbVal = false; + EXPECT_FALSE(sUnion.uVal.tbVal); + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_TRUE(sUnion.uVal.tbVal); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaVarBasedUnionIntegralTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(202); + sUnion.uVal.tsVal = -10; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tsVal, -11); + sUnion.switch_to(203); + sUnion.uVal.tusVal = 10; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tusVal, 11u); + sUnion.switch_to(204); + sUnion.uVal.tlVal = -20; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tlVal, -21); + sUnion.switch_to(205); + sUnion.uVal.tulVal = 20; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tulVal, 21u); + sUnion.switch_to(206); + sUnion.uVal.tllVal = -30; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tllVal, -31); + sUnion.switch_to(207); + sUnion.uVal.tullVal = 30; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tullVal, 31u); + sUnion.switch_to(208); + sUnion.uVal.ti8Val = -40; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(static_cast(sUnion.uVal.ti8Val), static_cast(-41)); // Cast needed for ARM compatibility + sUnion.switch_to(209); + sUnion.uVal.tui8Val = 40; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tui8Val, 41u); + sUnion.switch_to(210); + sUnion.uVal.ti16Val = -50; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ti16Val, -51); + sUnion.switch_to(211); + sUnion.uVal.tui16Val = 50; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tui16Val, 51u); + sUnion.switch_to(212); + sUnion.uVal.ti32Val = -60; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ti32Val, -61); + sUnion.switch_to(213); + sUnion.uVal.tui32Val = 60; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tui32Val, 61u); + sUnion.switch_to(214); + sUnion.uVal.ti64Val = -70; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.ti64Val, -71); + sUnion.switch_to(215); + sUnion.uVal.tui64Val = 70; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tui64Val, 71u); + sUnion.switch_to(223); + sUnion.uVal.tnVal = 80; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tnVal, 81); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaVarBasedUnionCharacterTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(216); + sUnion.uVal.tcVal = 'A'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tcVal, 'B'); + sUnion.switch_to(217); + sUnion.uVal.tc16Val = u'D'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tc16Val, u'E'); + sUnion.switch_to(218); + sUnion.uVal.tc32Val = U'G'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tc32Val, U'H'); + sUnion.switch_to(219); + sUnion.uVal.twcVal = L'J'; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.twcVal, L'K'); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaVarBasedUnionFloatingPointTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(220); + float fVal = -1234.5678f; + sUnion.uVal.tfVal = fVal; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tfVal, fVal - 1000.000f); + sUnion.switch_to(221); + double dVal = 8765.4321; + sUnion.uVal.tdVal = dVal; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tdVal, dVal + 1000.000); + // GCC issue with generated initialization in the consructor for the "long double" type: BUG #3982727 + //sUnion.switch_to(222); + //long double ldVal = -1234.4321; + //sUnion.uVal.tldVal = ldVal; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + //EXPECT_EQ(sUnion.uVal.tldVal, ldVal - 1000.000); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaVarBasedUnionEnumTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(224); + sUnion.uVal.teHelloVal = EHello::hello; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.teHelloVal, EHello::hallo); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaVarBasedUnionStringTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(226); + sUnion.uVal.tssVal = "hi"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tssVal, "hij"); + sUnion.switch_to(227); + sUnion.uVal.tss8Val = "huhu"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tss8Val, "huhuv"); + sUnion.switch_to(228); + sUnion.uVal.tss16Val = u"hey"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tss16Val, u"heyz"); + sUnion.switch_to(229); + sUnion.uVal.tss32Val = U"hello"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tss32Val, U"hellop"); + sUnion.switch_to(230); + sUnion.uVal.twssVal = L"servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.twssVal, L"servust"); + sUnion.switch_to(231); + sUnion.uVal.tssFixVal = "hi"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tssFixVal, "hij"); + sUnion.switch_to(232); + sUnion.uVal.tss8FixVal = "huhu"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tss8FixVal, "huhuv"); + sUnion.switch_to(233); + sUnion.uVal.tss16FixVal = u"hey"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tss16FixVal, u"heyz"); + sUnion.switch_to(234); + sUnion.uVal.tss32FixVal = U"hello"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tss32FixVal, U"hellop"); + sUnion.switch_to(235); + sUnion.uVal.twssFixVal = L"servus"; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.twssFixVal, L"servust"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +// Using typedefs of template based types produced an error: BUG #398266 +TEST(IPC_Communication_Test_UDS_Tunnel, DISABLED_MarshallMegaVarBasedUnionPointerTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(236); + //sUnion.uVal.tptr8Val.resize(100); + //for (size_t nIndex = 0; nIndex < sUnion.uVal.tptr8Val.size(); nIndex++) + // sUnion.uVal.tptr8Val[nIndex] = static_cast(nIndex); + //EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + //EXPECT_EQ(sUnion.uVal.tptr8Val.size(), 200); + //for (size_t nIndex = 0; nIndex < sUnion.uVal.tptr8Val.size() / 2; nIndex++) + // EXPECT_EQ(sUnion.uVal.tptr8Val[nIndex], sUnion.uVal.tptr8Val[nIndex + sUnion.uVal.tptr8Val.size() / 2]); + //sUnion.switch_to(237); + //sUnion.uVal.tptrssVal.resize(3); + //sUnion.uVal.tptrssVal[0] = "hello"; + //sUnion.uVal.tptrssVal[1] = "huhu"; + //sUnion.uVal.tptrssVal[2] = "hi"; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + //EXPECT_EQ(sUnion.uVal.tptrssVal.size(), 6); + //for (size_t nIndex = 0; nIndex < sUnion.uVal.tptrssVal.size() / 2; nIndex++) + // EXPECT_EQ(sUnion.uVal.tptrssVal[nIndex], sUnion.uVal.tptrssVal[nIndex + sUnion.uVal.tptrssVal.size() / 2]); + //sUnion.switch_to(238); + //sUnion.uVal.tptr8FixVal.resize(sUnion.uVal.tptr8FixVal.capacity()); + //for (size_t nIndex = 0; nIndex < sUnion.uVal.tptr8FixVal.size(); nIndex++) + // sUnion.uVal.tptr8FixVal[nIndex] = static_cast(nIndex); + //EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + //EXPECT_EQ(sUnion.uVal.tptr8FixVal.size(), 20); + //for (size_t nIndex = 0; nIndex < sUnion.uVal.tptr8FixVal.size(); nIndex++) + // EXPECT_EQ(sUnion.uVal.tptr8FixVal[nIndex], static_cast(nIndex << 1)); + //sUnion.switch_to(239); + //sUnion.uVal.tptrssFixVal.resize(sUnion.uVal.tptrssFixVal.capacity()); + //sUnion.uVal.tptrssFixVal[0] = "hi"; + //sUnion.uVal.tptrssFixVal[1] = "hoi"; + //sUnion.uVal.tptrssFixVal[2] = "hello"; + //sUnion.uVal.tptrssFixVal[3] = "hallo"; + //sUnion.uVal.tptrssFixVal[4] = "servus"; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + //EXPECT_EQ(sUnion.uVal.tptrssFixVal.size(), 5); + //EXPECT_EQ(sUnion.uVal.tptrssFixVal[0], "hihi"); + //EXPECT_EQ(sUnion.uVal.tptrssFixVal[1], "hoihoi"); + //EXPECT_EQ(sUnion.uVal.tptrssFixVal[2], "hellohello"); + //EXPECT_EQ(sUnion.uVal.tptrssFixVal[3], "hallohallo"); + //EXPECT_EQ(sUnion.uVal.tptrssFixVal[4], "servusservus"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +// Using typedefs of template based types produced an error: BUG #398266 +TEST(IPC_Communication_Test_UDS_Tunnel, DISABLED_MarshallMegaVarBasedUnionSequenceTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(236); + //sUnion.uVal.tseq8Val.resize(100); + //for (size_t nIndex = 0; nIndex < sUnion.uVal.tseq8Val.size(); nIndex++) + // sUnion.uVal.tseq8Val[nIndex] = static_cast(nIndex); + //EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + //EXPECT_EQ(sUnion.uVal.tseq8Val.size(), 200); + //for (size_t nIndex = 0; nIndex < sUnion.uVal.tseq8Val.size() / 2; nIndex++) + // EXPECT_EQ(sUnion.uVal.tseq8Val[nIndex], sUnion.uVal.tseq8Val[nIndex + sUnion.uVal.tseq8Val.size() / 2]); + //sUnion.switch_to(237); + //sUnion.uVal.tseqssVal.resize(3); + //sUnion.uVal.tseqssVal[0] = "hello"; + //sUnion.uVal.tseqssVal[1] = "huhu"; + //sUnion.uVal.tseqssVal[2] = "hi"; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + //EXPECT_EQ(sUnion.uVal.tseqssVal.size(), 6); + //for (size_t nIndex = 0; nIndex < sUnion.uVal.tseqssVal.size() / 2; nIndex++) + // EXPECT_EQ(sUnion.uVal.tseqssVal[nIndex], sUnion.uVal.tseqssVal[nIndex + sUnion.uVal.tseqssVal.size() / 2]); + //sUnion.switch_to(238); + //sUnion.uVal.tseq8FixVal.resize(sUnion.uVal.tseq8FixVal.capacity()); + //for (size_t nIndex = 0; nIndex < sUnion.uVal.tseq8FixVal.size(); nIndex++) + // sUnion.uVal.tseq8FixVal[nIndex] = static_cast(nIndex); + //EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + //EXPECT_EQ(sUnion.uVal.tseq8FixVal.size(), 20); + //for (size_t nIndex = 0; nIndex < sUnion.uVal.tseq8FixVal.size(); nIndex++) + // EXPECT_EQ(sUnion.uVal.tseq8FixVal[nIndex], static_cast(nIndex << 1)); + //sUnion.switch_to(239); + //sUnion.uVal.tseqssFixVal.resize(sUnion.uVal.tseqssFixVal.capacity()); + //sUnion.uVal.tseqssFixVal[0] = "hi"; + //sUnion.uVal.tseqssFixVal[1] = "hoi"; + //sUnion.uVal.tseqssFixVal[2] = "hello"; + //sUnion.uVal.tseqssFixVal[3] = "hallo"; + //sUnion.uVal.tseqssFixVal[4] = "servus"; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + //EXPECT_EQ(sUnion.uVal.tseqssFixVal.size(), 5); + //EXPECT_EQ(sUnion.uVal.tseqssFixVal[0], "hihi"); + //EXPECT_EQ(sUnion.uVal.tseqssFixVal[1], "hoihoi"); + //EXPECT_EQ(sUnion.uVal.tseqssFixVal[2], "hellohello"); + //EXPECT_EQ(sUnion.uVal.tseqssFixVal[3], "hallohallo"); + //EXPECT_EQ(sUnion.uVal.tseqssFixVal[4], "servusservus"); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaVarBasedUnionInterfaceTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + struct SMultiply : public IMultiplyValue + { + size_t Multiply(size_t n) + { + return n * n; + } + } sMultiply; + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(225); + + sUnion.uVal.tidVal = sdv::GetInterfaceId(); + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tidVal, sdv::GetInterfaceId()); + sUnion.switch_to(244); + sUnion.uVal.tifcVal = static_cast(&sMultiply); + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tifcVal.id(), sdv::GetInterfaceId()); + ASSERT_NE(sUnion.uVal.tifcVal, nullptr); + ASSERT_NE(sUnion.uVal.tifcVal.get(), nullptr); + EXPECT_EQ(sUnion.uVal.tifcVal.get()->Add(99), 19602); // 99 * 99 + 99 * 99 + // Typedef of interfaces current not possible. BUG #399464 + //sUnion.switch_to(245); + //sUnion.uVal.tpMultiplyValue = &sMultiply; + //EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + //ASSERT_NE(sUnion.uVal.tpMultiplyValue, nullptr); + //EXPECT_EQ(sUnion.uVal.tpMultiplyValue->Multiply(5), 625); // 5 * 5 * 5 * 5 + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} + +TEST(IPC_Communication_Test_UDS_Tunnel, MarshallMegaVarBasedUnionSubCompoundTypedef) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" + +[Console] +Report = "Silent" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + // Start communication control + CCommunicationControl control; + control.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::initialized); + control.SetOperationMode(sdv::EOperationMode::configuring); + EXPECT_EQ(control.GetObjectState(), sdv::EObjectState::configuring); + + // Load the shared memory components + LoadIPCModules(control, ELocalIpcBackend::tunnel); + + // Create the server connection + CInterfaceTest test; + sdv::u8string ssConnectionString; + EXPECT_TRUE(control.CreateServerConnection(sdv::com::EChannelType::local_channel, &test, 100, ssConnectionString) != 0u); + + // Create the client connection + sdv::IInterfaceAccess* pObjectProxy = nullptr; + EXPECT_TRUE(control.CreateClientConnection(ssConnectionString, 1000, pObjectProxy) != 0u); + ASSERT_NE(pObjectProxy, nullptr); + + // Get the target interface + IMegaTest* pMegaTest = pObjectProxy->GetInterface(); + + ASSERT_NE(pMegaTest, nullptr); + SMegaVarBasedUnion sUnion; + sUnion.switch_to(246); + sUnion.uVal.tsSubVal.i = 1; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tsSubVal.i, 2); + sUnion.switch_to(247); + sUnion.uVal.tsIndVal.i = 10; + EXPECT_NO_THROW(pMegaTest->ProcessMegaVarBasedUnion(sUnion)); + EXPECT_EQ(sUnion.uVal.tsIndVal.i, 20); + + // Cleanup... + control.Shutdown(); + appcontrol.Shutdown(); +} diff --git a/tests/unit_tests/ipc_com/main.cpp b/tests/unit_tests/ipc_com/main.cpp index 5c630d3..6003015 100644 --- a/tests/unit_tests/ipc_com/main.cpp +++ b/tests/unit_tests/ipc_com/main.cpp @@ -47,7 +47,8 @@ extern "C" int main(int argc, char* argv[]) } else { - CProcessWatchdog watchdog; + // Use extended execution time of 10 minutes + CProcessWatchdog watchdog(600u); ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); diff --git a/tests/unit_tests/ipc_connect/CMakeLists.txt b/tests/unit_tests/ipc_connect/CMakeLists.txt index 07125a3..156c45b 100644 --- a/tests/unit_tests/ipc_connect/CMakeLists.txt +++ b/tests/unit_tests/ipc_connect/CMakeLists.txt @@ -17,8 +17,10 @@ project (UnitTest_IPC_Connect VERSION 1.0 LANGUAGES CXX) # Compile the source code add_executable(UnitTest_IPC_Connect "main.cpp" - - "ipc_connect.cpp" + "ipc_connect_shared_mem.cpp" + "ipc_connect_false_positiv.cpp" + "ipc_connect_uds.cpp" + "ipc_connect_uds_tunnel.cpp" ) target_link_libraries(UnitTest_IPC_Connect ${CMAKE_DL_LIBS} GTest::GTest) diff --git a/tests/unit_tests/ipc_connect/ipc_connect.cpp b/tests/unit_tests/ipc_connect/ipc_connect.cpp deleted file mode 100644 index 9a7804e..0000000 --- a/tests/unit_tests/ipc_connect/ipc_connect.cpp +++ /dev/null @@ -1,307 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2025-2026 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Erik Verhoeven - initial API and implementation - ********************************************************************************/ - -#include "../../include/gtest_custom.h" -#include -#include -#include -#include "../../../sdv_services/ipc_connect/listener.h" -#include "../../../sdv_services/ipc_connect/client.h" - -TEST(IPC_Connect_Test, InstantiateLocalDefaultListener) -{ - // Initialize system - sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"config( -[Application] -Mode = "Essential" - -[LogHandler] -ViewFilter = "Fatal" -)config")); - ASSERT_TRUE(appcontrol.IsRunning()); - sdv::core::IModuleControl* pModuleControl = sdv::core::GetObject("ModuleControlService"); - ASSERT_NE(pModuleControl, nullptr); - EXPECT_NE(pModuleControl->Load("ipc_com.sdv"), 0); - EXPECT_NE(pModuleControl->Load("ipc_shared_mem.sdv"), 0); - EXPECT_NE(pModuleControl->Load("core_ps.sdv"), 0); - sdv::core::IRepositoryControl* pRepositoryControl = sdv::core::GetObject("RepositoryService"); - ASSERT_NE(pRepositoryControl, nullptr); - EXPECT_NE(pRepositoryControl->CreateObject("CommunicationControl", {}, {}), 0); - EXPECT_NE(pRepositoryControl->CreateObject("DefaultSharedMemoryChannelControl", {}, {}), 0); - - // Start listener - CListener listener; - EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::initialization_pending); - listener.Initialize(R"code([Listener] -Type = "Local" -)code"); - EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::initialized); - - // Shutdown - listener.Shutdown(); - EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::destruction_pending); - appcontrol.Shutdown(); -} - -TEST(IPC_Connect_Test, InstantiateLocalDedicatedListener) -{ - // Initialize system - sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"config( -[Application] -Mode = "Essential" - -[LogHandler] -ViewFilter = "Fatal" -)config")); - ASSERT_TRUE(appcontrol.IsRunning()); - sdv::core::IModuleControl* pModuleControl = sdv::core::GetObject("ModuleControlService"); - ASSERT_NE(pModuleControl, nullptr); - EXPECT_NE(pModuleControl->Load("ipc_com.sdv"), 0); - EXPECT_NE(pModuleControl->Load("ipc_shared_mem.sdv"), 0); - EXPECT_NE(pModuleControl->Load("core_ps.sdv"), 0); - sdv::core::IRepositoryControl* pRepositoryControl = sdv::core::GetObject("RepositoryService"); - ASSERT_NE(pRepositoryControl, nullptr); - EXPECT_NE(pRepositoryControl->CreateObject("CommunicationControl", {}, {}), 0); - EXPECT_NE(pRepositoryControl->CreateObject("DefaultSharedMemoryChannelControl", {}, {}), 0); - - // Start listener - CListener listener; - EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::initialization_pending); - listener.Initialize(R"code([Listener] -Type = "Local" -Instance = 1234 -)code"); - EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::initialized); - - // Shutdown - listener.Shutdown(); - EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::destruction_pending); - appcontrol.Shutdown(); -} - -TEST(IPC_Connect_Test, InstantiateLocalDefaultClientNoListener) -{ - // Initialize system - sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"config( -[Application] -Mode = "Essential" - -[LogHandler] -ViewFilter = "Fatal" -)config")); - ASSERT_TRUE(appcontrol.IsRunning()); - sdv::core::IModuleControl* pModuleControl = sdv::core::GetObject("ModuleControlService"); - ASSERT_NE(pModuleControl, nullptr); - EXPECT_NE(pModuleControl->Load("ipc_com.sdv"), 0); - EXPECT_NE(pModuleControl->Load("ipc_shared_mem.sdv"), 0); - EXPECT_NE(pModuleControl->Load("core_ps.sdv"), 0); - sdv::core::IRepositoryControl* pRepositoryControl = sdv::core::GetObject("RepositoryService"); - ASSERT_NE(pRepositoryControl, nullptr); - EXPECT_NE(pRepositoryControl->CreateObject("CommunicationControl", {}, {}), 0); - EXPECT_NE(pRepositoryControl->CreateObject("DefaultSharedMemoryChannelControl", {}, {}), 0); - - // Start client - CClient client; - EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::initialization_pending); - client.Initialize(""); - EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::initialized); - sdv::TObjectPtr ptrClient = client.Connect(R"code([Client] -Type = "local" -)code"); - EXPECT_FALSE(ptrClient); - - // Shutdown - ptrClient.Clear(); - client.Shutdown(); - EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::destruction_pending); - appcontrol.Shutdown(); -} - -TEST(IPC_Connect_Test, InstantiateLocalSpecificClientNoListener) -{ - // Initialize system - sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"config( -[Application] -Mode = "Essential" - -[LogHandler] -ViewFilter = "Fatal" -)config")); - ASSERT_TRUE(appcontrol.IsRunning()); - sdv::core::IModuleControl* pModuleControl = sdv::core::GetObject("ModuleControlService"); - ASSERT_NE(pModuleControl, nullptr); - EXPECT_NE(pModuleControl->Load("ipc_com.sdv"), 0); - EXPECT_NE(pModuleControl->Load("ipc_shared_mem.sdv"), 0); - EXPECT_NE(pModuleControl->Load("core_ps.sdv"), 0); - sdv::core::IRepositoryControl* pRepositoryControl = sdv::core::GetObject("RepositoryService"); - ASSERT_NE(pRepositoryControl, nullptr); - EXPECT_NE(pRepositoryControl->CreateObject("CommunicationControl", {}, {}), 0); - EXPECT_NE(pRepositoryControl->CreateObject("DefaultSharedMemoryChannelControl", {}, {}), 0); - - // Start client - CClient client; - EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::initialization_pending); - client.Initialize(""); - EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::initialized); - sdv::TObjectPtr ptrClient = client.Connect(R"code([Client] -Type = "local" -Instance = 1234 -)code"); - EXPECT_FALSE(ptrClient); - - // Shutdown - ptrClient.Clear(); - client.Shutdown(); - EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::destruction_pending); - appcontrol.Shutdown(); -} - -// Disabled the following test due to an unidentified crash/heap corruption occurring with MINGW on the build-server. -// Bug-report #610009 describes this issue: https://dev.azure.com/SW4ZF/AZP-074_DivDI_SofDCarResearch/_workitems/edit/610009 -#ifdef _WIN32 -TEST(IPC_Connect_Test, DISABLED_InstantiateLocalDefaultClientAndListener) -#else -TEST(IPC_Connect_Test, InstantiateLocalDefaultClientAndListener) -#endif -{ - // Initialize system - sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"config( -[Application] -Mode = "Essential" - -[LogHandler] -ViewFilter = "Fatal" -)config")); - ASSERT_TRUE(appcontrol.IsRunning()); - sdv::core::IModuleControl* pModuleControl = sdv::core::GetObject("ModuleControlService"); - ASSERT_NE(pModuleControl, nullptr); - EXPECT_NE(pModuleControl->Load("ipc_com.sdv"), 0); - EXPECT_NE(pModuleControl->Load("ipc_shared_mem.sdv"), 0); - EXPECT_NE(pModuleControl->Load("core_ps.sdv"), 0); - sdv::core::IRepositoryControl* pRepositoryControl = sdv::core::GetObject("RepositoryService"); - ASSERT_NE(pRepositoryControl, nullptr); - EXPECT_NE(pRepositoryControl->CreateObject("CommunicationControl", {}, {}), 0); - EXPECT_NE(pRepositoryControl->CreateObject("DefaultSharedMemoryChannelControl", {}, {}), 0); - - // Start listener - CListener listener; - EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::initialization_pending); - listener.Initialize(R"code([Listener] -Type = "Local" -)code"); - EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::initialized); - - // Start client - CClient client; - EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::initialization_pending); - client.Initialize(""); - EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::initialized); - sdv::TObjectPtr ptrClient = client.Connect(R"code([Client] -Type = "Local" -)code"); - EXPECT_TRUE(ptrClient); - - // The client is a pointer to the repository. Request the module control service - sdv::core::IObjectAccess* pObjectAccess = ptrClient.GetInterface(); - EXPECT_NE(pObjectAccess, nullptr); - sdv::core::IModuleInfo* pModuleInfo = nullptr; - if (pObjectAccess) - pModuleInfo = sdv::TInterfaceAccessPtr(pObjectAccess->GetObject("ModuleControlService")).GetInterface(); - EXPECT_NE(pModuleInfo, nullptr); - if (pModuleInfo) - { - EXPECT_FALSE(pModuleInfo->GetModuleList().empty()); - } - - // Shutdown - ptrClient.Clear(); - client.Shutdown(); - EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::destruction_pending); - listener.Shutdown(); - EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::destruction_pending); - appcontrol.Shutdown(); -} - -// Disabled the following test due to an unidentified crash/heap corruption occurring with MINGW on the build-server. -// Bug-report #610009 describes this issue: https://dev.azure.com/SW4ZF/AZP-074_DivDI_SofDCarResearch/_workitems/edit/610009 -#ifdef _WIN32 -TEST(IPC_Connect_Test, DISABLED_InstantiateLocalSpecificClientAndListener) -#else -TEST(IPC_Connect_Test, InstantiateLocalSpecificClientAndListener) -#endif -{ - // Initialize system - sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"config( -[Application] -Mode = "Essential" - -[LogHandler] -ViewFilter = "Fatal" -)config")); - ASSERT_TRUE(appcontrol.IsRunning()); - sdv::core::IModuleControl* pModuleControl = sdv::core::GetObject("ModuleControlService"); - ASSERT_NE(pModuleControl, nullptr); - EXPECT_NE(pModuleControl->Load("ipc_com.sdv"), 0); - EXPECT_NE(pModuleControl->Load("ipc_shared_mem.sdv"), 0); - EXPECT_NE(pModuleControl->Load("core_ps.sdv"), 0); - sdv::core::IRepositoryControl* pRepositoryControl = sdv::core::GetObject("RepositoryService"); - ASSERT_NE(pRepositoryControl, nullptr); - EXPECT_NE(pRepositoryControl->CreateObject("CommunicationControl", {}, {}), 0); - EXPECT_NE(pRepositoryControl->CreateObject("DefaultSharedMemoryChannelControl", {}, {}), 0); - - // Start listener - CListener listener; - EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::initialization_pending); - listener.Initialize(R"code([Listener] -Type = "Local" -Instance = 1234 -)code"); - EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::initialized); - - // Start client - CClient client; - EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::initialization_pending); - client.Initialize(""); - EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::initialized); - sdv::TObjectPtr ptrClient = client.Connect(R"code([Client] -Type = "Local" -Instance = 1234 -)code"); - EXPECT_TRUE(ptrClient); - - // The client is a pointer to the repository. Request the module control service - sdv::core::IObjectAccess* pObjectAccess = ptrClient.GetInterface(); - EXPECT_NE(pObjectAccess, nullptr); - sdv::core::IModuleInfo* pModuleInfo = nullptr; - if (pObjectAccess) - pModuleInfo = sdv::TInterfaceAccessPtr(pObjectAccess->GetObject("ModuleControlService")).GetInterface(); - EXPECT_NE(pModuleInfo, nullptr); - if (pModuleInfo) - { - EXPECT_FALSE(pModuleInfo->GetModuleList().empty()); - } - - // Shutdown - ptrClient.Clear(); - client.Shutdown(); - EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::destruction_pending); - listener.Shutdown(); - EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::destruction_pending); - appcontrol.Shutdown(); -} diff --git a/tests/unit_tests/ipc_connect/ipc_connect_false_positiv.cpp b/tests/unit_tests/ipc_connect/ipc_connect_false_positiv.cpp new file mode 100644 index 0000000..0275985 --- /dev/null +++ b/tests/unit_tests/ipc_connect/ipc_connect_false_positiv.cpp @@ -0,0 +1,86 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#include +#include +#include + +#include "../../../sdv_services/ipc_connect/client.h" +#include "../../../sdv_services/ipc_connect/listener.h" +#include "../../include/gtest_custom.h" + +TEST(IPC_Connect_Test, InstantiateListenerWithUnknownProvider) +{ + // Initialize system + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + sdv::core::IModuleControl* pModuleControl = sdv::core::GetObject("ModuleControlService"); + ASSERT_NE(pModuleControl, nullptr); + EXPECT_NE(pModuleControl->Load("ipc_com.sdv"), 0); + EXPECT_NE(pModuleControl->Load("ipc_shared_mem.sdv"), 0); + EXPECT_NE(pModuleControl->Load("core_ps.sdv"), 0); + sdv::core::IRepositoryControl* pRepositoryControl = sdv::core::GetObject("RepositoryService"); + ASSERT_NE(pRepositoryControl, nullptr); + EXPECT_NE(pRepositoryControl->CreateObject("CommunicationControl", {}, {}), 0); + EXPECT_NE(pRepositoryControl->CreateObject("DefaultSharedMemory", {}, {}), 0); + + // Start listener + CListener listener; + EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::initialization_pending); + listener.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::initialization_failure); + + // Shutdown + listener.Shutdown(); + EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::destruction_pending); + appcontrol.Shutdown(); +} + +TEST(IPC_Connect_Test_Shared_Mem, InstantiateClientWithoutConfiguration) +{ + // Initialize system + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + sdv::core::IModuleControl* pModuleControl = sdv::core::GetObject("ModuleControlService"); + ASSERT_NE(pModuleControl, nullptr); + EXPECT_NE(pModuleControl->Load("ipc_com.sdv"), 0); + EXPECT_NE(pModuleControl->Load("ipc_shared_mem.sdv"), 0); + EXPECT_NE(pModuleControl->Load("core_ps.sdv"), 0); + sdv::core::IRepositoryControl* pRepositoryControl = sdv::core::GetObject("RepositoryService"); + ASSERT_NE(pRepositoryControl, nullptr); + EXPECT_NE(pRepositoryControl->CreateObject("CommunicationControl", {}, {}), 0); + + // Start client without configuration + CClientConnect client; + EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::initialization_pending); + client.Initialize(sdv::SObjectInfo()); + EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::initialization_failure); + + client.Shutdown(); + EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::destruction_pending); + appcontrol.Shutdown(); +} diff --git a/tests/unit_tests/ipc_connect/ipc_connect_shared_mem.cpp b/tests/unit_tests/ipc_connect/ipc_connect_shared_mem.cpp new file mode 100644 index 0000000..f19d3cc --- /dev/null +++ b/tests/unit_tests/ipc_connect/ipc_connect_shared_mem.cpp @@ -0,0 +1,181 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#include "../../include/gtest_custom.h" +#include +#include +#include +#include +#include "../../../sdv_services/ipc_connect/listener.h" +#include "../../../sdv_services/ipc_connect/client.h" + +TEST(IPC_Connect_Test_Shared_Mem, InstantiateListener) +{ + // Initialize system + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" +Instance = 1234 + +[LogHandler] +ViewFilter = "Fatal" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + sdv::core::IModuleControl* pModuleControl = sdv::core::GetObject("ModuleControlService"); + ASSERT_NE(pModuleControl, nullptr); + EXPECT_NE(pModuleControl->Load("ipc_com.sdv"), 0); + EXPECT_NE(pModuleControl->Load("ipc_shared_mem.sdv"), 0); + EXPECT_NE(pModuleControl->Load("core_ps.sdv"), 0); + sdv::core::IRepositoryControl* pRepositoryControl = sdv::core::GetObject("RepositoryService"); + ASSERT_NE(pRepositoryControl, nullptr); + EXPECT_NE(pRepositoryControl->CreateObject("CommunicationControl", {}, {}), 0); + EXPECT_NE(pRepositoryControl->CreateObject("DefaultSharedMemory", {}, {}), 0); + + sdv::SObjectInfo sListenerInfo{}; + sListenerInfo.ssConfig = R"toml( +[Provider] +Name = "DefaultSharedMemory" + +[IpcChannel] +Name = "MyPersonalChannelName" +)toml"; + + // Start listener + CListener listener; + EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::initialization_pending); + listener.Initialize(sListenerInfo); + EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::initialized); + + EXPECT_EQ(listener.GetProviderName(), "DefaultSharedMemory"); + + // Shutdown + listener.Shutdown(); + EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::destruction_pending); + appcontrol.Shutdown(); +} + +TEST(IPC_Connect_Test_Shared_Mem, InstantiateClientNoListener) +{ + // Initialize system + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + sdv::core::IModuleControl* pModuleControl = sdv::core::GetObject("ModuleControlService"); + ASSERT_NE(pModuleControl, nullptr); + EXPECT_NE(pModuleControl->Load("ipc_com.sdv"), 0); + EXPECT_NE(pModuleControl->Load("ipc_shared_mem.sdv"), 0); + EXPECT_NE(pModuleControl->Load("core_ps.sdv"), 0); + sdv::core::IRepositoryControl* pRepositoryControl = sdv::core::GetObject("RepositoryService"); + ASSERT_NE(pRepositoryControl, nullptr); + EXPECT_NE(pRepositoryControl->CreateObject("CommunicationControl", {}, {}), 0); + + sdv::SObjectInfo sClientInfo{}; + sClientInfo.ssConfig = R"toml( +[Provider] +Name = "DefaultSharedMemory" + +[IpcChannel] +Name = "MyPersonalChannelName" +)toml"; + + // Start client + CClientConnect client; + EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::initialization_pending); + client.Initialize(sClientInfo); + EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::initialized); + EXPECT_FALSE(client.Connect()); + + // Shutdown + client.Shutdown(); + EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::destruction_pending); + appcontrol.Shutdown(); +} + +TEST(IPC_Connect_Test_Shared_Mem, InstantiateClientAndListener) +{ + // Initialize system + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + sdv::core::IModuleControl* pModuleControl = sdv::core::GetObject("ModuleControlService"); + ASSERT_NE(pModuleControl, nullptr); + EXPECT_NE(pModuleControl->Load("ipc_com.sdv"), 0); + EXPECT_NE(pModuleControl->Load("ipc_shared_mem.sdv"), 0); + EXPECT_NE(pModuleControl->Load("core_ps.sdv"), 0); + sdv::core::IRepositoryControl* pRepositoryControl = sdv::core::GetObject("RepositoryService"); + ASSERT_NE(pRepositoryControl, nullptr); + EXPECT_NE(pRepositoryControl->CreateObject("CommunicationControl", {}, {}), 0); + + sdv::SObjectInfo sListenerInfo{}; + sListenerInfo.ssConfig = R"toml([Provider] +Name = "DefaultSharedMemory" +[IpcChannel] +Name = "CHANNEL_1234" +)toml"; + + // Start listener + CListener listener; + EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::initialization_pending); + listener.Initialize(sListenerInfo); + EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::initialized); + + sdv::SObjectInfo sClientInfo{}; + sClientInfo.ssConfig = R"toml([Provider] +Name = "DefaultSharedMemory" +[IpcChannel] +Name = "CHANNEL_1234" +)toml"; + + // Start client + CClientConnect client; + EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::initialization_pending); + client.Initialize(sClientInfo); + EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::initialized); + EXPECT_TRUE(client.Connect()); + EXPECT_TRUE(client.IsConnected()); + + // The client is a pointer to the repository. Request the module control service + sdv::core::IObjectAccess* pObjectAccess = + sdv::TInterfaceAccessPtr(client.GetRemoteRepository()).GetInterface(); + EXPECT_NE(pObjectAccess, nullptr); + sdv::core::IModuleInfo* pModuleInfo = nullptr; + if (pObjectAccess) + pModuleInfo = sdv::TInterfaceAccessPtr(pObjectAccess->GetObject("ModuleControlService")).GetInterface(); + EXPECT_NE(pModuleInfo, nullptr); + if (pModuleInfo) + { + EXPECT_FALSE(pModuleInfo->GetModuleList().empty()); + } + EXPECT_TRUE(client.Disconnect()); + + // Shutdown + client.Shutdown(); + EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::destruction_pending); + listener.Shutdown(); + EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::destruction_pending); + appcontrol.Shutdown(); +} + diff --git a/tests/unit_tests/ipc_connect/ipc_connect_uds.cpp b/tests/unit_tests/ipc_connect/ipc_connect_uds.cpp new file mode 100644 index 0000000..bc2409c --- /dev/null +++ b/tests/unit_tests/ipc_connect/ipc_connect_uds.cpp @@ -0,0 +1,233 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Denisa Ros - initial API and implementation + ********************************************************************************/ + +#include "../../include/gtest_custom.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include "../../../sdv_services/ipc_connect/listener.h" +#include "../../../sdv_services/ipc_connect/client.h" + +namespace +{ + +const char* GetUdsModuleName() +{ +#ifdef _WIN32 + return "uds_win_sockets.sdv"; +#else + return "uds_unix_sockets.sdv"; +#endif +} + +const char* GetUdsChannelControlClassName() +{ +#ifdef _WIN32 + return "WinSocketsChannelControl"; +#else + return "UnixSocketsChannelControl"; +#endif +} + +std::string UniqueSuffix() +{ + const auto now = std::chrono::steady_clock::now().time_since_epoch().count(); + return std::to_string(static_cast(now)); +} + +std::string MakeChannelName(const char* base) +{ + return std::string(base) + "_" + UniqueSuffix(); +} + +std::string MakeUdsConfigByName(const std::string& channelName) +{ + return std::string("[Provider]\n") + + "Name = \"unix_domain_sockets\"\n\n" + + "[IpcChannel]\n" + + "Name = \"" + channelName + "\"\n"; +} + +void LoadRequiredModulesAndObjects(bool createUdsChannelControl = true) +{ + auto* pModuleControl = + sdv::core::GetObject("ModuleControlService"); + ASSERT_NE(pModuleControl, nullptr); + + EXPECT_NE(pModuleControl->Load("ipc_com.sdv"), 0); + EXPECT_NE(pModuleControl->Load(GetUdsModuleName()), 0); + EXPECT_NE(pModuleControl->Load("core_ps.sdv"), 0); + + auto* pRepositoryControl = + sdv::core::GetObject("RepositoryService"); + ASSERT_NE(pRepositoryControl, nullptr); + + EXPECT_NE(pRepositoryControl->CreateObject("CommunicationControl", {}, {}), 0); + + if (createUdsChannelControl) + { + EXPECT_NE( + pRepositoryControl->CreateObject(GetUdsChannelControlClassName(), {}, {}), + 0); + } +} + +} // anonymous namespace + +TEST(IPC_Connect_Test_UDS, InstantiateListener) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" +Instance = 1234 + +[LogHandler] +ViewFilter = "Fatal" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + LoadRequiredModulesAndObjects(true); + + const std::string channelName = MakeChannelName("MyPersonalChannelName"); + + sdv::SObjectInfo sListenerInfo{}; + sListenerInfo.ssConfig = MakeUdsConfigByName(channelName); + + CListener listener; + EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::initialization_pending); + + listener.Initialize(sListenerInfo); + + EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::initialized); + EXPECT_EQ(listener.GetProviderName(), "unix_domain_sockets"); + + listener.Shutdown(); + EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::destruction_pending); + + appcontrol.Shutdown(); +} + +TEST(IPC_Connect_Test_UDS, InstantiateClientNoListener) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + LoadRequiredModulesAndObjects(true); + + const std::string channelName = MakeChannelName("MyPersonalChannelName"); + + sdv::SObjectInfo sClientInfo{}; + sClientInfo.ssConfig = MakeUdsConfigByName(channelName); + + CClientConnect client; + EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::initialization_pending); + client.Initialize(sClientInfo); + + EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::initialized); + std::cout << "Connecting client..." << std::endl; + EXPECT_FALSE(client.Connect()); + std::cout << "Client failed to connect." << std::endl; + EXPECT_FALSE(client.IsConnected()); + + client.Shutdown(); + EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::destruction_pending); + appcontrol.Shutdown(); +} + +TEST(IPC_Connect_Test_UDS, InstantiateClientAndListener) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + LoadRequiredModulesAndObjects(true); + + const std::string channelName = MakeChannelName("CHANNEL_1234"); + + sdv::SObjectInfo sListenerInfo{}; + sListenerInfo.ssConfig = MakeUdsConfigByName(channelName); + + CListener listener; + EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::initialization_pending); + + listener.Initialize(sListenerInfo); + + EXPECT_EQ(listener.GetProviderName(), "unix_domain_sockets"); + EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::initialized); + + // Small delay to avoid startup races between listener init and client connect. + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + sdv::SObjectInfo sClientInfo{}; + sClientInfo.ssConfig = MakeUdsConfigByName(channelName); + + CClientConnect client; + EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::initialization_pending); + + client.Initialize(sClientInfo); + + EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::initialized); + EXPECT_TRUE(client.Connect()); + EXPECT_TRUE(client.IsConnected()); + + // The client should expose the remote repository after successful connect. + sdv::core::IObjectAccess* pObjectAccess = + sdv::TInterfaceAccessPtr(client.GetRemoteRepository()) + .GetInterface(); + EXPECT_NE(pObjectAccess, nullptr); + + sdv::core::IModuleInfo* pModuleInfo = nullptr; + if (pObjectAccess) + { + pModuleInfo = + sdv::TInterfaceAccessPtr(pObjectAccess->GetObject("ModuleControlService")) + .GetInterface(); + } + + EXPECT_NE(pModuleInfo, nullptr); + if (pModuleInfo) + { + EXPECT_FALSE(pModuleInfo->GetModuleList().empty()); + } + + EXPECT_TRUE(client.Disconnect()); + std::cout << "Client Shutdown" << std::endl; + client.Shutdown(); + EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::destruction_pending); + std::cout << "Listener Shutdown" << std::endl; + listener.Shutdown(); + EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::destruction_pending); + + appcontrol.Shutdown(); +} \ No newline at end of file diff --git a/tests/unit_tests/ipc_connect/ipc_connect_uds_tunnel.cpp b/tests/unit_tests/ipc_connect/ipc_connect_uds_tunnel.cpp new file mode 100644 index 0000000..47bf524 --- /dev/null +++ b/tests/unit_tests/ipc_connect/ipc_connect_uds_tunnel.cpp @@ -0,0 +1,278 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Denisa Ros - initial API and implementation + ********************************************************************************/ + +#include "../../include/gtest_custom.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include "../../../sdv_services/ipc_connect/listener.h" +#include "../../../sdv_services/ipc_connect/client.h" + +namespace +{ + +const char* GetTunnelModuleName() +{ +#ifdef _WIN32 + return "uds_win_tunnel.sdv"; +#else + return "uds_unix_tunnel.sdv"; +#endif +} + +const char* GetTunnelChannelControlClassName() +{ +#ifdef _WIN32 + return "WinTunnelChannelControl"; +#else + return "UnixTunnelChannelControl"; +#endif +} + +std::string UniqueSuffix() +{ + const auto now = std::chrono::steady_clock::now().time_since_epoch().count(); + return std::to_string(static_cast(now)); +} + +std::string MakeChannelName(const char* base) +{ + return std::string(base) + "_" + UniqueSuffix(); +} + +std::string MakeTunnelName(const char* base) +{ + return std::string("t_") + base + "_" + UniqueSuffix(); +} + +std::string MakeTunnelConfigByName(const std::string& channelName, + const std::string& tunnelName) +{ + return std::string("[Provider]\n") + + "Name = \"unix_domain_sockets_tunnel\"\n\n" + + "[IpcChannel]\n" + + "Name = \"" + channelName + "\"\n" + + "Tunnel = \"" + tunnelName + "\"\n"; +} + +void LoadRequiredModulesAndObjects(bool createTunnelChannelControl = true) +{ + auto* pModuleControl = + sdv::core::GetObject("ModuleControlService"); + ASSERT_NE(pModuleControl, nullptr); + + EXPECT_NE(pModuleControl->Load("ipc_com.sdv"), 0); + EXPECT_NE(pModuleControl->Load(GetTunnelModuleName()), 0); + EXPECT_NE(pModuleControl->Load("core_ps.sdv"), 0); + + auto* pRepositoryControl = + sdv::core::GetObject("RepositoryService"); + ASSERT_NE(pRepositoryControl, nullptr); + + EXPECT_NE(pRepositoryControl->CreateObject("CommunicationControl", {}, {}), 0); + + if (createTunnelChannelControl) + { + EXPECT_NE( + pRepositoryControl->CreateObject(GetTunnelChannelControlClassName(), {}, {}), + 0); + } +} + +} // anonymous namespace + +TEST(IPC_Connect_Test_UDS_Tunnel, InstantiateListener) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" +Instance = 1234 + +[LogHandler] +ViewFilter = "Fatal" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + LoadRequiredModulesAndObjects(true); + + const std::string channelName = MakeChannelName("MyPersonalChannelName"); + const std::string tunnelName = MakeTunnelName("listener"); + + sdv::SObjectInfo sListenerInfo{}; + sListenerInfo.ssConfig = MakeTunnelConfigByName(channelName, tunnelName); + + CListener listener; + EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::initialization_pending); + + listener.Initialize(sListenerInfo); + + EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::initialized); + EXPECT_EQ(listener.GetProviderName(), "unix_domain_sockets_tunnel"); + + listener.Shutdown(); + EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::destruction_pending); + + appcontrol.Shutdown(); +} + +TEST(IPC_Connect_Test_UDS_Tunnel, InstantiateClientNoListener) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + LoadRequiredModulesAndObjects(true); + + const std::string channelName = MakeChannelName("MyPersonalChannelName"); + const std::string tunnelName = MakeTunnelName("no_listener"); + + sdv::SObjectInfo sClientInfo{}; + sClientInfo.ssConfig = MakeTunnelConfigByName(channelName, tunnelName); + + CClientConnect client; + EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::initialization_pending); + + client.Initialize(sClientInfo); + + EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::initialized); + EXPECT_FALSE(client.Connect()); + EXPECT_FALSE(client.IsConnected()); + + client.Shutdown(); + EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::destruction_pending); + + appcontrol.Shutdown(); +} + +TEST(IPC_Connect_Test_UDS_Tunnel, InstantiateClientAndListener) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + LoadRequiredModulesAndObjects(true); + + const std::string channelName = MakeChannelName("CHANNEL_1234"); + const std::string tunnelName = MakeTunnelName("client_listener"); + + sdv::SObjectInfo sListenerInfo{}; + sListenerInfo.ssConfig = MakeTunnelConfigByName(channelName, tunnelName); + + CListener listener; + EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::initialization_pending); + + listener.Initialize(sListenerInfo); + + EXPECT_EQ(listener.GetProviderName(), "unix_domain_sockets_tunnel"); + EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::initialized); + + // Allow listener endpoint publication to complete before client connect. + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + sdv::SObjectInfo sClientInfo{}; + sClientInfo.ssConfig = MakeTunnelConfigByName(channelName, tunnelName); + + CClientConnect client; + EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::initialization_pending); + + client.Initialize(sClientInfo); + + EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::initialized); + EXPECT_TRUE(client.Connect()); + EXPECT_TRUE(client.IsConnected()); + + // The client should expose the remote repository after successful connect. + sdv::core::IObjectAccess* pObjectAccess = + sdv::TInterfaceAccessPtr(client.GetRemoteRepository()) + .GetInterface(); + EXPECT_NE(pObjectAccess, nullptr); + + sdv::core::IModuleInfo* pModuleInfo = nullptr; + if (pObjectAccess) + { + pModuleInfo = + sdv::TInterfaceAccessPtr(pObjectAccess->GetObject("ModuleControlService")) + .GetInterface(); + } + + EXPECT_NE(pModuleInfo, nullptr); + if (pModuleInfo) + { + EXPECT_FALSE(pModuleInfo->GetModuleList().empty()); + } + + EXPECT_TRUE(client.Disconnect()); + + client.Shutdown(); + EXPECT_EQ(client.GetObjectState(), sdv::EObjectState::destruction_pending); + + listener.Shutdown(); + EXPECT_EQ(listener.GetObjectState(), sdv::EObjectState::destruction_pending); + + appcontrol.Shutdown(); +} + +TEST(IPC_Connect_Test_UDS_Tunnel, InvalidProvider_ShouldNotInitializeClient) +{ + sdv::app::CAppControl appcontrol; + ASSERT_TRUE(appcontrol.Startup(R"config( +[Application] +Mode = "Essential" + +[LogHandler] +ViewFilter = "Fatal" +)config")); + ASSERT_TRUE(appcontrol.IsRunning()); + + LoadRequiredModulesAndObjects(true); + + sdv::SObjectInfo sClientInfo{}; + sClientInfo.ssConfig = R"toml( +[Provider] +Name = "invalid_tunnel_provider" + +[IpcChannel] +Name = "CHANNEL_1234" +Tunnel = "t_invalid_provider" +)toml"; + + CClientConnect client; + client.Initialize(sClientInfo); + + EXPECT_NE(client.GetObjectState(), sdv::EObjectState::initialized); + EXPECT_FALSE(client.Connect()); + EXPECT_FALSE(client.IsConnected()); + + client.Shutdown(); + appcontrol.Shutdown(); +} \ No newline at end of file diff --git a/tests/unit_tests/module_control/main.cpp b/tests/unit_tests/module_control/main.cpp index 8a8e354..bd16c43 100644 --- a/tests/unit_tests/module_control/main.cpp +++ b/tests/unit_tests/module_control/main.cpp @@ -21,6 +21,7 @@ #include "../../../sdv_services/core/toml_parser/character_reader_utf_8.cpp" #include "../../../sdv_services/core/toml_parser/miscellaneous.cpp" #include "../../../sdv_services/core/toml_parser/code_snippet.cpp" +#include "../../../sdv_services/core/toml_parser/parser_node_indexer.cpp" #include "../../../sdv_services/core/module_control.cpp" #include "../../../sdv_services/core/module.cpp" #include "../../../sdv_services/core/app_config.cpp" diff --git a/tests/unit_tests/module_control/mock.h b/tests/unit_tests/module_control/mock.h index 9f147fb..b008725 100644 --- a/tests/unit_tests/module_control/mock.h +++ b/tests/unit_tests/module_control/mock.h @@ -46,6 +46,7 @@ public: bool IsConsoleSilent() { return true; } bool IsConsoleVerbose() { return false; } uint32_t GetInstanceID() { return 1234u; } + std::filesystem::path GetFrameworkDir() const { return GetExecDirectory(); } std::filesystem::path GetRootDir() const { return GetExecDirectory(); } std::filesystem::path GetInstallDir() const { return GetExecDirectory(); } std::vector GetSystemConfigPaths() const { return {}; } @@ -117,7 +118,4 @@ inline CRepository& GetRepository() #include "../../../sdv_services/core/app_settings.h" #include "../../../sdv_services/core/app_config.h" -//inline std::filesystem::path GetCoreDirectoryMock() { return "../../bin"; } -//#define GetCoreDirectory GetCoreDirectoryMock - #endif // !defined MOCK_H \ No newline at end of file diff --git a/tests/unit_tests/named_mutex/named_mutex_test.cpp b/tests/unit_tests/named_mutex/named_mutex_test.cpp index c1a62c2..c16e3c6 100644 --- a/tests/unit_tests/named_mutex/named_mutex_test.cpp +++ b/tests/unit_tests/named_mutex/named_mutex_test.cpp @@ -17,6 +17,16 @@ #include #include #include +#include + +namespace +{ +std::string MakeUniqueMutexName(const char* suffix) +{ + return std::string("HELLO_") + suffix + "_" + + std::to_string(std::chrono::high_resolution_clock::now().time_since_epoch().count()); +} +} #if defined(_WIN32) && defined(_UNICODE) extern "C" int wmain(int argc, wchar_t* argv[]) @@ -32,12 +42,14 @@ extern "C" int main(int argc, char* argv[]) TEST(NamedMutexTest, Construction) { - ipc::named_mutex mtx("HELLO"); + ipc::named_mutex mtx(MakeUniqueMutexName("Construction")); EXPECT_NE(mtx.native_handle(), nullptr); } TEST(NamedMutexTest, CritSectSyncManualLock) { + const std::string mutexName = MakeUniqueMutexName("CritSectSyncManualLock"); + // Counter function check for correct counter value. // The checking is manipulated by the bEnable flag. When disabled, no sync will be done and the check will fail. When enabled, // sync will be done and the check will succeed. @@ -46,7 +58,7 @@ TEST(NamedMutexTest, CritSectSyncManualLock) std::atomic_bool bEnable = false; auto fn = [&]() { - ipc::named_mutex mtx("HELLO"); + ipc::named_mutex mtx(mutexName); if (bEnable) mtx.lock(); @@ -81,12 +93,14 @@ TEST(NamedMutexTest, CritSectSyncManualLock) TEST(NamedMutexTest, CritSectSyncAutoLock) { + const std::string mutexName = MakeUniqueMutexName("CritSectSyncAutoLock"); + // Counter function check for correct counter value. int32_t iCnt = 0; std::atomic_bool bSuccess = true; auto fn = [&]() { - ipc::named_mutex mtx("HELLO"); + ipc::named_mutex mtx(mutexName); std::unique_lock lock(mtx); bSuccess = bSuccess && (iCnt == 0); @@ -108,10 +122,12 @@ TEST(NamedMutexTest, CritSectSyncAutoLock) TEST(NamedMutexTest, TryLock) { + const std::string mutexName = MakeUniqueMutexName("TryLock"); + std::atomic_bool bRunning = false; auto fn = [&]() { - ipc::named_mutex mtx("HELLO"); + ipc::named_mutex mtx(mutexName); mtx.lock(); while (bRunning) std::this_thread::sleep_for(std::chrono::milliseconds(1)); @@ -123,7 +139,7 @@ TEST(NamedMutexTest, TryLock) std::this_thread::sleep_for(std::chrono::milliseconds(250)); // Try locking; doesn't work since thread still locks. - ipc::named_mutex mtx2("HELLO"); + ipc::named_mutex mtx2(mutexName); EXPECT_FALSE(mtx2.try_lock()); bRunning = false; @@ -137,7 +153,7 @@ TEST(NamedMutexTest, TryLock) TEST(NamedMutexTest, Naming) { - ipc::named_mutex mtx1("HELLO"); + ipc::named_mutex mtx1(MakeUniqueMutexName("Naming")); EXPECT_FALSE(mtx1.name().empty()); ipc::named_mutex mtx2; diff --git a/tests/unit_tests/parameters/param_access.cpp b/tests/unit_tests/parameters/param_access.cpp index f5ad753..cec4c9f 100644 --- a/tests/unit_tests/parameters/param_access.cpp +++ b/tests/unit_tests/parameters/param_access.cpp @@ -834,22 +834,23 @@ public: InitParamMap(); } + BEGIN_SDV_PARAM_MAP() SDV_PARAM_SET_READONLY() - SDV_PARAM_ENTRY(m_i, "my_integer", 10, "int_unit", "My integer") + SDV_PARAM_ENTRY(m_i, "my_integer", 20, "int_unit", "My integer") SDV_PARAM_RESET_READONLY() SDV_PARAM_ENTRY(m_d, "my_double", 1234.5, "double_unit", "My double") SDV_PARAM_SET_READONLY() - SDV_PARAM_ENTRY(m_ss, "my_string", "string_value", "string_unit", "My string") + SDV_PARAM_ENTRY(m_ss, "my_string", "value_string", "string_unit", "My string") SDV_PARAM_RESET_ATTRIBUTES() SDV_PARAM_ENTRY(m_b, "my_boolean", true, "no_unit", "My boolean") END_SDV_PARAM_MAP() private: - int m_i; - double m_d; - std::string m_ss; - bool m_b; + int m_i = 10; // Read only; will not be overwritten + double m_d = 5432.1; // Writable, will be overwritten + std::string m_ss = "string_value"; // Read only; will not be overwritten + bool m_b = false; // Writable, will be overwritten }; TEST(ParameterTest, ReadOnlyParamMapStaticInfo) @@ -869,7 +870,7 @@ TEST(ParameterTest, ReadOnlyParamMapStaticInfo) EXPECT_EQ(ptrParamInfo->Name(), "my_integer"); EXPECT_EQ(ptrParamInfo->Unit(), "int_unit"); EXPECT_EQ(ptrParamInfo->Description(), "My integer"); - EXPECT_EQ(ptrParamInfo->DefaultVal(), 10); + EXPECT_EQ(ptrParamInfo->DefaultVal(), 20); EXPECT_TRUE(ptrParamInfo->ReadOnly()); // Get parameter #1 global information @@ -889,7 +890,7 @@ TEST(ParameterTest, ReadOnlyParamMapStaticInfo) EXPECT_EQ(ptrParamInfo->Name(), "my_string"); EXPECT_EQ(ptrParamInfo->Unit(), "string_unit"); EXPECT_EQ(ptrParamInfo->Description(), "My string"); - EXPECT_EQ(ptrParamInfo->DefaultVal(), "string_value"); + EXPECT_EQ(ptrParamInfo->DefaultVal(), "value_string"); EXPECT_TRUE(ptrParamInfo->ReadOnly()); // Get parameter #3 global information @@ -922,7 +923,7 @@ TEST(ParameterTest, ReadOnlyParamMapObjectInfo) EXPECT_TRUE(ptrParamInfo->Numeric()); EXPECT_EQ(ptrParamInfo->Unit(), "int_unit"); EXPECT_EQ(ptrParamInfo->Description(), "My integer"); - EXPECT_EQ(ptrParamInfo->DefaultVal(), 10); + EXPECT_EQ(ptrParamInfo->DefaultVal(), 20); EXPECT_TRUE(ptrParamInfo->ReadOnly()); // Get parameter #1 global information @@ -940,7 +941,7 @@ TEST(ParameterTest, ReadOnlyParamMapObjectInfo) EXPECT_TRUE(ptrParamInfo->String()); EXPECT_EQ(ptrParamInfo->Unit(), "string_unit"); EXPECT_EQ(ptrParamInfo->Description(), "My string"); - EXPECT_EQ(ptrParamInfo->DefaultVal(), "string_value"); + EXPECT_EQ(ptrParamInfo->DefaultVal(), "value_string"); EXPECT_TRUE(ptrParamInfo->ReadOnly()); // Get parameter #3 global information @@ -971,7 +972,7 @@ TEST(ParameterTest, ReadOnlyParamMapObjectInfoIndirect) EXPECT_TRUE(info.Numeric()); EXPECT_EQ(info.Unit(), "int_unit"); EXPECT_EQ(info.Description(), "My integer"); - EXPECT_EQ(info.DefaultVal(), 10); + EXPECT_EQ(info.DefaultVal(), 20); EXPECT_TRUE(info.ReadOnly()); // Get parameter #1 global information @@ -987,7 +988,7 @@ TEST(ParameterTest, ReadOnlyParamMapObjectInfoIndirect) EXPECT_TRUE(info.String()); EXPECT_EQ(info.Unit(), "string_unit"); EXPECT_EQ(info.Description(), "My string"); - EXPECT_EQ(info.DefaultVal(), "string_value"); + EXPECT_EQ(info.DefaultVal(), "value_string"); EXPECT_TRUE(info.ReadOnly()); // Get parameter #2 global information diff --git a/tests/unit_tests/permission_control/CMakeLists.txt b/tests/unit_tests/permission_control/CMakeLists.txt new file mode 100644 index 0000000..9bacda3 --- /dev/null +++ b/tests/unit_tests/permission_control/CMakeLists.txt @@ -0,0 +1,33 @@ +#******************************************************************************* +# Copyright (c) 2025-2026 ZF Friedrichshafen AG +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# +# Contributors: +# Erik Verhoeven - initial API and implementation +#******************************************************************************* + +# Define project +project (UnitTest_PermissionControl VERSION 1.0 LANGUAGES CXX) + +# Compile the source code +add_executable(UnitTest_PermissionControl + "main.cpp" + "permission_control_tests.cpp") +target_link_libraries(UnitTest_PermissionControl ${CMAKE_DL_LIBS} GTest::GTest) + +# Add the IDL Compiler unittest +add_test(NAME UnitTest_PermissionControl COMMAND UnitTest_PermissionControl) + +# Execute the test +add_custom_command(TARGET UnitTest_PermissionControl POST_BUILD + COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake "$" --gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_PermissionControl.xml + VERBATIM +) + +# Build dependencies +add_dependencies(UnitTest_PermissionControl dependency_sdv_components) diff --git a/tests/unit_tests/app_connect/main.cpp b/tests/unit_tests/permission_control/main.cpp similarity index 93% rename from tests/unit_tests/app_connect/main.cpp rename to tests/unit_tests/permission_control/main.cpp index e988681..6563070 100644 --- a/tests/unit_tests/app_connect/main.cpp +++ b/tests/unit_tests/permission_control/main.cpp @@ -13,7 +13,7 @@ #include #include "../../../global/process_watchdog.h" -#include "../../../global/localmemmgr.h" +#include "../../../sdv_services/core/permission_control.cpp" /** * @brief Main function @@ -26,7 +26,6 @@ extern "C" int main(int argc, char* argv[]) { CProcessWatchdog watchdog; - CLocalMemMgr memmgr; ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } diff --git a/tests/unit_tests/permission_control/permission_control_tests.cpp b/tests/unit_tests/permission_control/permission_control_tests.cpp new file mode 100644 index 0000000..e96fb45 --- /dev/null +++ b/tests/unit_tests/permission_control/permission_control_tests.cpp @@ -0,0 +1,264 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#include +#include "../../include/gtest_custom.h" +#include "../../../sdv_services/core/permission_control.h" + +TEST(PermissionControl, DefaultRestriction) +{ + CPermissionControl control; + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::restricted_access); +} + +TEST(PermissionControl, SetRestrictionInternal) +{ + CPermissionControl control; + std::vector vecPermissions; + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::restricted_access); + + // Full access + vecPermissions.emplace_back(control.CreatePermissionObject(sdv::core::EAccessPermission::full_access)); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::full_access); + + // Reduce access to local access + vecPermissions.emplace_back(control.CreatePermissionObject(sdv::core::EAccessPermission::local_access)); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::local_access); + + // Reduce access to remote access + vecPermissions.emplace_back(control.CreatePermissionObject(sdv::core::EAccessPermission::remote_access)); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::remote_access); + + // Reduce access to restricted access + vecPermissions.emplace_back(control.CreatePermissionObject(sdv::core::EAccessPermission::restricted_access)); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::restricted_access); + + // Try increase access to full access - this should not have any change + vecPermissions.emplace_back(control.CreatePermissionObject(sdv::core::EAccessPermission::full_access)); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::restricted_access); + + // Remove the local access index #1 - this should not have any change + EXPECT_EQ(vecPermissions[1].Permission(), sdv::core::EAccessPermission::local_access); + vecPermissions.erase(vecPermissions.begin() + 1); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::restricted_access); + + // Remove first full access index #0 - this should not have any change + EXPECT_EQ(vecPermissions[0].Permission(), sdv::core::EAccessPermission::full_access); + vecPermissions.erase(vecPermissions.begin()); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::restricted_access); + + // Remove remote access new index #0 - this should not have any change + EXPECT_EQ(vecPermissions[0].Permission(), sdv::core::EAccessPermission::remote_access); + vecPermissions.erase(vecPermissions.begin()); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::restricted_access); + + // Remove restricted access new index #0 - access will change to full access + EXPECT_EQ(vecPermissions[0].Permission(), sdv::core::EAccessPermission::restricted_access); + vecPermissions.erase(vecPermissions.begin()); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::full_access); + + // Remove the remaining access - this would change to (default) restricted access again + vecPermissions.clear(); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::restricted_access); +} + +TEST(PermissionControl, SetRestrictionExternal) +{ + CPermissionControl control; + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::restricted_access); + + // Try to set external permission to full access; this will not work + EXPECT_EQ(control.RestrictAccessPermission(sdv::core::EAccessPermission::full_access), 0u); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::restricted_access); + + // Try to set external permission to local access; this will not work + EXPECT_EQ(control.RestrictAccessPermission(sdv::core::EAccessPermission::local_access), 0u); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::restricted_access); + + // Try to set external permission to remote access; this will not work + EXPECT_EQ(control.RestrictAccessPermission(sdv::core::EAccessPermission::remote_access), 0u); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::restricted_access); + + // Try to set external permission to restricted access; this will not work + EXPECT_EQ(control.RestrictAccessPermission(sdv::core::EAccessPermission::restricted_access), 0u); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::restricted_access); +} + +TEST(PermissionControl, InitializeRestrictionInternal_SetRestrictionExternal) +{ + CPermissionControl control; + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::restricted_access); + + // Set restriction to full access + std::optional optPermission = control.CreatePermissionObject(sdv::core::EAccessPermission::full_access); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::full_access); + + // Set external permission to local access + sdv::core::TPermissionID permidLocalAccess = control.RestrictAccessPermission(sdv::core::EAccessPermission::local_access); + EXPECT_NE(permidLocalAccess, 0u); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::local_access); + + // Set external permission to remote access + sdv::core::TPermissionID permidRemoteAccess = control.RestrictAccessPermission(sdv::core::EAccessPermission::remote_access); + EXPECT_NE(permidRemoteAccess, 0u); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::remote_access); + + // Set external permission to restricted access + sdv::core::TPermissionID permidRestrictedAccess = + control.RestrictAccessPermission(sdv::core::EAccessPermission::restricted_access); + EXPECT_NE(permidRestrictedAccess, 0u); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::restricted_access); + + // Set external permission to full access - this will not change the access + sdv::core::TPermissionID permidFullAccess = control.RestrictAccessPermission(sdv::core::EAccessPermission::full_access); + EXPECT_NE(permidFullAccess, 0u); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::restricted_access); + + // Release not existing access ID - should not work + EXPECT_FALSE(control.ReleaseAccessPermission(0)); + EXPECT_FALSE(control.ReleaseAccessPermission(1)); + + // Release local, restricted and full access - this will change access to remote access + EXPECT_TRUE(control.ReleaseAccessPermission(permidLocalAccess)); + EXPECT_TRUE(control.ReleaseAccessPermission(permidRestrictedAccess)); + EXPECT_TRUE(control.ReleaseAccessPermission(permidFullAccess)); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::remote_access); + + // Release local again - should not work + EXPECT_FALSE(control.ReleaseAccessPermission(permidLocalAccess)); + + // Terminate internal full access - access should stay with remote access + optPermission.reset(); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::remote_access); + + // Release remote access - the access permission will change to restricted access again + EXPECT_TRUE(control.ReleaseAccessPermission(permidRemoteAccess)); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::restricted_access); +} + +TEST(PermissionControl, DefaultThreadPermission) +{ + CPermissionControl control; + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::restricted_access); + + // Set restriction to full access + CAccessPermission permission = control.CreatePermissionObject(sdv::core::EAccessPermission::full_access); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::full_access); + + // Start new thread - access should be restricted + std::thread thread([&]() + { + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::restricted_access); + }); + thread.join(); +} + +TEST(PermissionControl, InternalSetThreadPermission) +{ + CPermissionControl control; + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::restricted_access); + + // Set restriction to local access + CAccessPermission permission = control.CreatePermissionObject(sdv::core::EAccessPermission::local_access); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::local_access); + + // Start new thread - access should be restricted + std::thread thread([&]() + { + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::restricted_access); + + // Set full access permission + CAccessPermission permissionFullAccess = control.CreatePermissionObject(sdv::core::EAccessPermission::full_access); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::full_access); + + // Set local access permission + CAccessPermission permissionLocalAccess = control.CreatePermissionObject(sdv::core::EAccessPermission::local_access); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::local_access); + + // Set remote access permission + CAccessPermission permissionRemoteAccess = control.CreatePermissionObject(sdv::core::EAccessPermission::remote_access); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::remote_access); + + // Set restricted access permission + CAccessPermission permissionRestrictedAccess = + control.CreatePermissionObject(sdv::core::EAccessPermission::restricted_access); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::restricted_access); + }); + thread.join(); + + // Access permissions on main thread should still be local + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::local_access); +} + +TEST(PermissionControl, TransferThreadPermission) +{ + CPermissionControl control; + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::restricted_access); + + // Set restriction to full access + CAccessPermission permissionFullAccess = control.CreatePermissionObject(sdv::core::EAccessPermission::full_access); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::full_access); + sdv::core::TPermissionID transferFullAccess = control.TransferCurrentPermission(); + EXPECT_NE(transferFullAccess, 0u); + + // Set restriction to local access + CAccessPermission permissionLocalAccess = control.CreatePermissionObject(sdv::core::EAccessPermission::local_access); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::local_access); + sdv::core::TPermissionID transferLocalAccess = control.TransferCurrentPermission(); + EXPECT_NE(transferLocalAccess, 0u); + + // Set restriction to remote access + CAccessPermission permissionRemoteAccess = control.CreatePermissionObject(sdv::core::EAccessPermission::remote_access); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::remote_access); + sdv::core::TPermissionID transferRemoteAccess = control.TransferCurrentPermission(); + EXPECT_NE(transferRemoteAccess, 0u); + + // Set restriction to restricted access + CAccessPermission permissionRestrictedAccess = control.CreatePermissionObject(sdv::core::EAccessPermission::restricted_access); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::restricted_access); + sdv::core::TPermissionID transferRestrictedAccess = control.TransferCurrentPermission(); + EXPECT_NE(transferRestrictedAccess, 0u); + + // Start new thread - access should be restricted + std::thread thread([&]() + { + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::restricted_access); + + // Tets bogus transfer IDs + EXPECT_FALSE(control.SetAccessPermission(0)); + EXPECT_FALSE(control.SetAccessPermission(1)); + + // Set full access permission from transfer ID + sdv::core::TPermissionID permidFullAccess = control.SetAccessPermission(transferFullAccess); + EXPECT_NE(permidFullAccess, 0u); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::full_access); + + // Set local access permission from transfer ID + sdv::core::TPermissionID permidLocalAccess = control.SetAccessPermission(transferLocalAccess); + EXPECT_NE(permidLocalAccess, 0u); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::local_access); + + // Set remote access permission from transfer ID + sdv::core::TPermissionID permidRemoteAccess = control.SetAccessPermission(transferRemoteAccess); + EXPECT_NE(permidRemoteAccess, 0u); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::remote_access); + + // Set restricted access permission from transfer ID + sdv::core::TPermissionID permidRestrictedAccess = control.SetAccessPermission(transferRestrictedAccess); + EXPECT_NE(permidRestrictedAccess, 0u); + EXPECT_EQ(control.GetCurrentPermission(), sdv::core::EAccessPermission::restricted_access); + + // At the end of the thread, the IDs will be cleaned up automatically. + }); + thread.join(); +} \ No newline at end of file diff --git a/tests/unit_tests/process_control/process_control_app.cpp b/tests/unit_tests/process_control/process_control_app.cpp index 2fa1f08..efe57fb 100644 --- a/tests/unit_tests/process_control/process_control_app.cpp +++ b/tests/unit_tests/process_control/process_control_app.cpp @@ -44,8 +44,8 @@ extern "C" int main(int argc, char* argv[]) EOperatingmode eMode = static_cast(std::atoi(sdv::MakeAnsiString(argv[1]).c_str())); sdv::app::CAppControl appcontrol; - if (!appcontrol.Startup(R"code([Application] -Mode="Maintenance")code")) + if (!appcontrol.Startup(R"toml([Application] +Mode="Maintenance")toml")) { std::cout << GetTimestamp() << "Failed to start app control..." << std::endl; return -1; @@ -89,7 +89,7 @@ Mode="Maintenance")code")) std::cout << GetTimestamp() << "Waiting for process with PID#" << std::dec << tProcessID << std::endl; CProcessMonitorHelper monitor; CProcessControl control; - control.Initialize(""); // Needed since local instantiation + control.Initialize(sdv::SObjectInfo()); // Needed since local instantiation uint32_t uiMon = control.RegisterMonitor(tProcessID, &monitor); if (!uiMon) { @@ -134,7 +134,7 @@ Mode="Maintenance")code")) std::cout << GetTimestamp() << "Terminate process with PID#" << std::dec << tProcessID << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Make certain, that the first process is actually running... CProcessControl control; - control.Initialize(""); // Needed since local instantiation + control.Initialize(sdv::SObjectInfo()); // Needed since local instantiation nResult = control.Terminate(tProcessID) ? 0 : -20; control.Shutdown(); // Needed to prevent clash with core } diff --git a/tests/unit_tests/process_control/process_control_tests.cpp b/tests/unit_tests/process_control/process_control_tests.cpp index 7ffb029..fe72cd7 100644 --- a/tests/unit_tests/process_control/process_control_tests.cpp +++ b/tests/unit_tests/process_control/process_control_tests.cpp @@ -23,8 +23,8 @@ TEST(ProcessControlTest, Instantiate) { sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"code([Application] -Mode="Maintenance")code")); + ASSERT_TRUE(appcontrol.Startup(R"toml([Application] +Mode="Maintenance")toml")); CProcessControl control; EXPECT_EQ(control.GetProcessID(), static_cast(getpid())); @@ -35,11 +35,11 @@ Mode="Maintenance")code")); TEST(ProcessControlTest, ParentRightsExecuteProcessNormalShutdown) { sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"code([Application] -Mode="Maintenance")code")); + ASSERT_TRUE(appcontrol.Startup(R"toml([Application] +Mode="Maintenance")toml")); CProcessControl control; - control.Initialize(""); // Needed since local instantiation + control.Initialize(sdv::SObjectInfo()); // Needed since local instantiation #ifdef _WIN32 const std::string ssModule = "UnitTest_ProcessControlApp.exe"; #else @@ -61,11 +61,11 @@ Mode="Maintenance")code")); TEST(ProcessControlTest, ParentRightsExecuteProcessEmergencyExit) { sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"code([Application] -Mode="Maintenance")code")); + ASSERT_TRUE(appcontrol.Startup(R"toml([Application] +Mode="Maintenance")toml")); CProcessControl control; - control.Initialize(""); // Needed since local instantiation + control.Initialize(sdv::SObjectInfo()); // Needed since local instantiation #ifdef _WIN32 const std::string ssModule = "UnitTest_ProcessControlApp.exe"; #else @@ -87,13 +87,13 @@ Mode="Maintenance")code")); TEST(ProcessControlTest, ParentRightsExecuteProcessNormalShutdownWithMonitor) { sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"code([Application] -Mode="Maintenance")code")); + ASSERT_TRUE(appcontrol.Startup(R"toml([Application] +Mode="Maintenance")toml")); CProcessMonitorHelper monitor; CProcessControl control; - control.Initialize(""); // Needed since local instantiation + control.Initialize(sdv::SObjectInfo()); // Needed since local instantiation #ifdef _WIN32 const std::string ssModule = "UnitTest_ProcessControlApp.exe"; #else @@ -119,13 +119,13 @@ Mode="Maintenance")code")); TEST(ProcessControlTest, ParentRightsExecuteProcessEmergencyExitWithMonitor) { sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"code([Application] -Mode="Maintenance")code")); + ASSERT_TRUE(appcontrol.Startup(R"toml([Application] +Mode="Maintenance")toml")); CProcessMonitorHelper monitor; CProcessControl control; - control.Initialize(""); // Needed since local instantiation + control.Initialize(sdv::SObjectInfo()); // Needed since local instantiation #ifdef _WIN32 const std::string ssModule = "UnitTest_ProcessControlApp.exe"; #else @@ -151,14 +151,14 @@ Mode="Maintenance")code")); TEST(ProcessControlTest, ParentRightsExecuteMultiProcessNormalShutdown) { sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"code([Application] -Mode="Maintenance")code")); + ASSERT_TRUE(appcontrol.Startup(R"toml([Application] +Mode="Maintenance")toml")); CProcessMonitorHelper monitor1; CProcessMonitorHelper monitor2; CProcessControl control; - control.Initialize(""); // Needed since local instantiation + control.Initialize(sdv::SObjectInfo()); // Needed since local instantiation #ifdef _WIN32 const std::string ssModule = "UnitTest_ProcessControlApp.exe"; #else @@ -198,14 +198,14 @@ Mode="Maintenance")code")); TEST(ProcessControlTest, ParentRightsExecuteMultiEmergencyAccess) { sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"code([Application] -Mode="Maintenance")code")); + ASSERT_TRUE(appcontrol.Startup(R"toml([Application] +Mode="Maintenance")toml")); CProcessMonitorHelper monitor1; CProcessMonitorHelper monitor2; CProcessControl control; - control.Initialize(""); // Needed since local instantiation + control.Initialize(sdv::SObjectInfo()); // Needed since local instantiation #ifdef _WIN32 const std::string ssModule = "UnitTest_ProcessControlApp.exe"; #else @@ -244,13 +244,13 @@ Mode="Maintenance")code")); TEST(ProcessControlTest, ParentRightsExecuteProcessAndTerminate) { sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"code([Application] -Mode="Maintenance")code")); + ASSERT_TRUE(appcontrol.Startup(R"toml([Application] +Mode="Maintenance")toml")); CProcessMonitorHelper monitor; CProcessControl control; - control.Initialize(""); // Needed since local instantiation + control.Initialize(sdv::SObjectInfo()); // Needed since local instantiation #ifdef _WIN32 const std::string ssModule = "UnitTest_ProcessControlApp.exe"; #else @@ -265,7 +265,7 @@ Mode="Maintenance")code")); EXPECT_NE(uiCookie, 0u); bool bTerminateResult = false; - std::thread thread([&]() + sdv::core::secure_thread thread([&]() { std::this_thread::sleep_for(std::chrono::milliseconds(250)); bTerminateResult = control.Terminate(tProcessID); @@ -285,14 +285,14 @@ Mode="Maintenance")code")); TEST(ProcessControlTest, ParentRightsExecuteMultiTerminate) { sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"code([Application] -Mode="Maintenance")code")); + ASSERT_TRUE(appcontrol.Startup(R"toml([Application] +Mode="Maintenance")toml")); CProcessMonitorHelper monitor1; CProcessMonitorHelper monitor2; CProcessControl control; - control.Initialize(""); // Needed since local instantiation + control.Initialize(sdv::SObjectInfo()); // Needed since local instantiation #ifdef _WIN32 const std::string ssModule = "UnitTest_ProcessControlApp.exe"; #else @@ -330,11 +330,11 @@ Mode="Maintenance")code")); TEST(ProcessControlTest, ReducedRightsExecuteProcessNormalShutdown) { sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"code([Application] -Mode="Maintenance")code")); + ASSERT_TRUE(appcontrol.Startup(R"toml([Application] +Mode="Maintenance")toml")); CProcessControl control; - control.Initialize(""); // Needed since local instantiation + control.Initialize(sdv::SObjectInfo()); // Needed since local instantiation #ifdef _WIN32 const std::string ssModule = "UnitTest_ProcessControlApp.exe"; #else @@ -356,11 +356,11 @@ Mode="Maintenance")code")); TEST(ProcessControlTest, ReducedRightsExecuteProcessEmergencyExit) { sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"code([Application] -Mode="Maintenance")code")); + ASSERT_TRUE(appcontrol.Startup(R"toml([Application] +Mode="Maintenance")toml")); CProcessControl control; - control.Initialize(""); // Needed since local instantiation + control.Initialize(sdv::SObjectInfo()); // Needed since local instantiation #ifdef _WIN32 const std::string ssModule = "UnitTest_ProcessControlApp.exe"; #else @@ -382,13 +382,13 @@ Mode="Maintenance")code")); TEST(ProcessControlTest, ReducedRightsExecuteProcessNormalShutdownWithMonitor) { sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"code([Application] -Mode="Maintenance")code")); + ASSERT_TRUE(appcontrol.Startup(R"toml([Application] +Mode="Maintenance")toml")); CProcessMonitorHelper monitor; CProcessControl control; - control.Initialize(""); // Needed since local instantiation + control.Initialize(sdv::SObjectInfo()); // Needed since local instantiation #ifdef _WIN32 const std::string ssModule = "UnitTest_ProcessControlApp.exe"; #else @@ -414,13 +414,13 @@ Mode="Maintenance")code")); TEST(ProcessControlTest, ReducedRightsExecuteProcessEmergencyExitWithMonitor) { sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"code([Application] -Mode="Maintenance")code")); + ASSERT_TRUE(appcontrol.Startup(R"toml([Application] +Mode="Maintenance")toml")); CProcessMonitorHelper monitor; CProcessControl control; - control.Initialize(""); // Needed since local instantiation + control.Initialize(sdv::SObjectInfo()); // Needed since local instantiation #ifdef _WIN32 const std::string ssModule = "UnitTest_ProcessControlApp.exe"; #else @@ -446,14 +446,14 @@ Mode="Maintenance")code")); TEST(ProcessControlTest, ReducedRightsExecuteMultiProcessNormalShutdown) { sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"code([Application] -Mode="Maintenance")code")); + ASSERT_TRUE(appcontrol.Startup(R"toml([Application] +Mode="Maintenance")toml")); CProcessMonitorHelper monitor1; CProcessMonitorHelper monitor2; CProcessControl control; - control.Initialize(""); // Needed since local instantiation + control.Initialize(sdv::SObjectInfo()); // Needed since local instantiation #ifdef _WIN32 const std::string ssModule = "UnitTest_ProcessControlApp.exe"; #else @@ -491,14 +491,14 @@ Mode="Maintenance")code")); TEST(ProcessControlTest, ReducedRightsExecuteMultiEmergencyAccess) { sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"code([Application] -Mode="Maintenance")code")); + ASSERT_TRUE(appcontrol.Startup(R"toml([Application] +Mode="Maintenance")toml")); CProcessMonitorHelper monitor1; CProcessMonitorHelper monitor2; CProcessControl control; - control.Initialize(""); // Needed since local instantiation + control.Initialize(sdv::SObjectInfo()); // Needed since local instantiation #ifdef _WIN32 const std::string ssModule = "UnitTest_ProcessControlApp.exe"; #else @@ -537,13 +537,13 @@ Mode="Maintenance")code")); TEST(ProcessControlTest, ReducedRightsExecuteProcessAndTerminate) { sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"code([Application] -Mode="Maintenance")code")); + ASSERT_TRUE(appcontrol.Startup(R"toml([Application] +Mode="Maintenance")toml")); CProcessMonitorHelper monitor; CProcessControl control; - control.Initialize(""); // Needed since local instantiation + control.Initialize(sdv::SObjectInfo()); // Needed since local instantiation #ifdef _WIN32 const std::string ssModule = "UnitTest_ProcessControlApp.exe"; #else @@ -558,7 +558,7 @@ Mode="Maintenance")code")); EXPECT_NE(uiCookie, 0u); bool bTerminateResult = false; - std::thread thread([&]() + sdv::core::secure_thread thread([&]() { std::this_thread::sleep_for(std::chrono::milliseconds(250)); bTerminateResult = control.Terminate(tProcessID); @@ -578,14 +578,14 @@ Mode="Maintenance")code")); TEST(ProcessControlTest, ReducedRightsExecuteMultiTerminate) { sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"code([Application] -Mode="Maintenance")code")); + ASSERT_TRUE(appcontrol.Startup(R"toml([Application] +Mode="Maintenance")toml")); CProcessMonitorHelper monitor1; CProcessMonitorHelper monitor2; CProcessControl control; - control.Initialize(""); // Needed since local instantiation + control.Initialize(sdv::SObjectInfo()); // Needed since local instantiation #ifdef _WIN32 const std::string ssModule = "UnitTest_ProcessControlApp.exe"; #else @@ -623,15 +623,15 @@ Mode="Maintenance")code")); TEST(ProcessControlTest, ExecuteProcessNormalShutdownWithMultipleMonitors) { sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"code([Application] -Mode="Maintenance")code")); + ASSERT_TRUE(appcontrol.Startup(R"toml([Application] +Mode="Maintenance")toml")); CProcessMonitorHelper monitor1; CProcessMonitorHelper monitor2; CProcessMonitorHelper monitor3; CProcessControl control; - control.Initialize(""); // Needed since local instantiation + control.Initialize(sdv::SObjectInfo()); // Needed since local instantiation #ifdef _WIN32 const std::string ssModule = "UnitTest_ProcessControlApp.exe"; #else diff --git a/tests/unit_tests/repository/main.cpp b/tests/unit_tests/repository/main.cpp index acb1378..aaf7568 100644 --- a/tests/unit_tests/repository/main.cpp +++ b/tests/unit_tests/repository/main.cpp @@ -21,9 +21,11 @@ #include "../../../sdv_services/core/toml_parser/character_reader_utf_8.cpp" #include "../../../sdv_services/core/toml_parser/miscellaneous.cpp" #include "../../../sdv_services/core/toml_parser/code_snippet.cpp" +#include "../../../sdv_services/core/toml_parser/parser_node_indexer.cpp" #include "../../../sdv_services/core/module_control.cpp" #include "../../../sdv_services/core/module.cpp" #include "../../../sdv_services/core/repository.cpp" +#include "../../../sdv_services/core/permission_control.cpp" #include "../../../sdv_services/core/iso_monitor.cpp" #include "../../../sdv_services/core/object_lifetime_control.cpp" #include "../../../sdv_services/core/app_config.cpp" diff --git a/tests/unit_tests/repository/mock.h b/tests/unit_tests/repository/mock.h index ed08560..7136fcb 100644 --- a/tests/unit_tests/repository/mock.h +++ b/tests/unit_tests/repository/mock.h @@ -15,6 +15,7 @@ #define MOCK_H #include +#include #include #include "../../../global/exec_dir_helper.h" #include "../../../sdv_services/core/installation_manifest.h" @@ -24,6 +25,7 @@ #define APP_CONTROL_H #define LOGGER_H #define LOGGER_CONTROL_H +//#define PERMISSION_CONTROL_H /** * @brief CAppSettings redefined @@ -45,6 +47,7 @@ public: bool IsConsoleSilent() { return true; } bool IsConsoleVerbose() { return false; } uint32_t GetInstanceID() { return 1000u; } + std::filesystem::path GetFrameworkDir() const { return GetExecDirectory(); } std::filesystem::path GetRootDir() const { return GetExecDirectory(); } std::filesystem::path GetInstallDir() const { return GetExecDirectory(); } std::vector GetSystemConfigPaths() const { return {}; } @@ -105,15 +108,36 @@ inline CLoggerControl& GetLoggerControl() return logger_control; } +///** +// * @brief CPermissionControl redefined +// */ +//class CPermissionControl : public sdv::IInterfaceAccess +//{ +//public: +// BEGIN_SDV_INTERFACE_MAP() +// END_SDV_INTERFACE_MAP() +// +// // CPermissionControl mocked functions +// virtual sdv::core::EAccessPermission GetCurrentPermission() const { return sdv::core::EAccessPermission::full_access; } +//}; +// +///** +// * @brief Return the permission control. +// * @return Reference to the permission control. +// */ +//inline CPermissionControl& GetPermissionControl() +//{ +// static CPermissionControl control; +// return control; +//} +// #include "../../../sdv_services/core/toml_parser/parser_toml.h" #include "../../../sdv_services/core/toml_parser/parser_node_toml.h" #include "../../../sdv_services/core/module_control.h" #include "../../../sdv_services/core/repository.h" +#include "../../../sdv_services/core/permission_control.h" #include "../../../sdv_services/core/app_config.h" -//inline std::filesystem::path GetCoreDirectoryMock() { return "../../bin"; } -//#define GetCoreDirectory GetCoreDirectoryMock - class CHelper { public: diff --git a/tests/unit_tests/repository/repository_test.cpp b/tests/unit_tests/repository/repository_test.cpp index c383aef..775411e 100644 --- a/tests/unit_tests/repository/repository_test.cpp +++ b/tests/unit_tests/repository/repository_test.cpp @@ -29,7 +29,7 @@ public: SDV_INTERFACE_ENTRY(sdv::IObjectControl) END_SDV_INTERFACE_MAP() - virtual void Initialize([[maybe_unused]] const sdv::u8string& ssObjectConfig) override + virtual void Initialize([[maybe_unused]] const sdv::SObjectInfo& sObjectInfo) override { FAIL() << "Error: Initialize should not be called by Repo Service!"; //m_eObjectState = sdv::EObjectState::initialization_failure; @@ -91,8 +91,8 @@ TEST(RepositoryTest, CreateNonexistentClass) CModuleControl modulectrl; CHelper helper(modulectrl, repository); ASSERT_TRUE(modulectrl.Load((GetExecDirectory() / "UnitTest_Repository_test_module.sdv").generic_u8string())); - EXPECT_FALSE(repository.CreateObject2("TestFooBar", nullptr, nullptr)); - repository.DestroyObject2("TestFooBar"); + EXPECT_FALSE(repository.CreateObject("TestFooBar", nullptr, nullptr)); + EXPECT_FALSE(repository.DestroyObject("TestFooBar")); } TEST(RepositoryTest, GetNonexistentObject) @@ -100,11 +100,11 @@ TEST(RepositoryTest, GetNonexistentObject) CRepository repository; // Must be created first CModuleControl modulectrl; CHelper helper(modulectrl, repository); + auto permission = GetPermissionControl().CreatePermissionObject(sdv::core::EAccessPermission::full_access); ASSERT_TRUE(modulectrl.Load((GetExecDirectory() / "UnitTest_Repository_test_module.sdv").generic_u8string())); - bool bRes = repository.CreateObject2("Example_Object", nullptr, nullptr); - EXPECT_TRUE(bRes); + EXPECT_TRUE(repository.CreateObject("Example_Object", nullptr, nullptr)); EXPECT_EQ(nullptr, repository.GetObject("TestFooBar")); - repository.DestroyObject2("Example_Object"); + EXPECT_TRUE(repository.DestroyObject("Example_Object")); } TEST(RepositoryTest, InstantiateAndGet) @@ -112,11 +112,12 @@ TEST(RepositoryTest, InstantiateAndGet) CRepository repository; // Must be created first CModuleControl modulectrl; CHelper helper(modulectrl, repository); + auto permission = GetPermissionControl().CreatePermissionObject(sdv::core::EAccessPermission::full_access); ASSERT_TRUE(modulectrl.Load((GetExecDirectory() / "UnitTest_Repository_test_module.sdv").generic_u8string())); - bool bRes = repository.CreateObject2("Example_Object", nullptr, nullptr); + bool bRes = repository.CreateObject("Example_Object", nullptr, nullptr); EXPECT_TRUE(bRes); EXPECT_NE(nullptr, repository.GetObject("Example_Object")); - repository.DestroyObject2("Example_Object"); + EXPECT_TRUE(repository.DestroyObject("Example_Object")); EXPECT_EQ(nullptr, repository.GetObject("Example_Object")); } @@ -126,8 +127,7 @@ TEST(RepositoryTest, InstantiateInitFail) CModuleControl modulectrl; CHelper helper(modulectrl, repository); ASSERT_TRUE(modulectrl.Load((GetExecDirectory() / "UnitTest_Repository_test_module.sdv").generic_u8string())); - bool bRes = repository.CreateObject2("TestObject_IObjectControlFail", nullptr, nullptr); - EXPECT_FALSE(bRes); + EXPECT_FALSE(repository.CreateObject("TestObject_IObjectControlFail", nullptr, nullptr)); EXPECT_EQ(nullptr, repository.GetObject("TestObject_IObjectControlFail")); - repository.DestroyObject2("TestObject_IObjectControlFail"); + EXPECT_FALSE(repository.DestroyObject("TestObject_IObjectControlFail")); } diff --git a/tests/unit_tests/scheduler/scheduler_test.cpp b/tests/unit_tests/scheduler/scheduler_test.cpp index f5bfd1f..0c27d00 100644 --- a/tests/unit_tests/scheduler/scheduler_test.cpp +++ b/tests/unit_tests/scheduler/scheduler_test.cpp @@ -15,7 +15,7 @@ #include #include #include "../../../global/process_watchdog.h" -#include "../../../global/scheduler/scheduler.cpp" +#include "../../../global/scheduler/scheduler.h" #if defined(_WIN32) && defined(_UNICODE) extern "C" int wmain(int argc, wchar_t* argv[]) @@ -31,7 +31,7 @@ extern "C" int main(int argc, char* argv[]) TEST(TaskSchedulerTest, Construction) { - CTaskScheduler scheduler(2); + CTaskScheduler scheduler; EXPECT_EQ(scheduler.GetThreadCount(), 2); EXPECT_EQ(scheduler.GetBusyThreadCount(), 0); EXPECT_EQ(scheduler.GetIdleThreadCount(), 2); @@ -50,7 +50,7 @@ TEST(TaskSchedulerTest, Construction) TEST(TaskSchedulerTest, PreallocatedConcurrencyExecution) { - CTaskScheduler scheduler(4); + CTaskScheduler scheduler; // Schedule tasks onto 4 threads std::atomic_size_t nExecuted = 0; @@ -74,7 +74,7 @@ TEST(TaskSchedulerTest, PreallocatedConcurrencyExecution) TEST(TaskSchedulerTest, AllocateConcurrency) { - CTaskScheduler scheduler(2); + CTaskScheduler scheduler; // Schedule 4 tasks onto two threads pre-allocated and two just-in-time-allocated threads std::atomic_size_t nExecuted = 0; @@ -98,7 +98,7 @@ TEST(TaskSchedulerTest, AllocateConcurrency) TEST(TaskSchedulerTest, ConcurrencyAndTaskQueueing) { - CTaskScheduler scheduler(2, 2); + CTaskScheduler scheduler; // Schedule 4 tasks onto two threads std::atomic_size_t nExecuted = 0; @@ -122,7 +122,7 @@ TEST(TaskSchedulerTest, ConcurrencyAndTaskQueueing) TEST(TaskSchedulerTest, ConcurrencyAndDisallowTaskQueueing) { - CTaskScheduler scheduler(2, 2); + CTaskScheduler scheduler; // Schedule 4 tasks onto two threads.. Disallow the second task to be queued and the fourth task to be queued. std::atomic_size_t nExecuted = 0; @@ -133,7 +133,8 @@ TEST(TaskSchedulerTest, ConcurrencyAndDisallowTaskQueueing) while (bWait) std::this_thread::sleep_for(std::chrono::milliseconds(100)); nExecuted++; - }, (n % 2 ? CTaskScheduler::EScheduleFlags::no_queue: CTaskScheduler::EScheduleFlags::normal)); + }, + (n % 2 ? EScheduleFlags::no_queue : EScheduleFlags::normal)); bWait = false; // Wait until all threads are finalized @@ -146,7 +147,7 @@ TEST(TaskSchedulerTest, ConcurrencyAndDisallowTaskQueueing) TEST(TaskSchedulerTest, ConcurrencyAndPrioritizedTaskQueueing) { - CTaskScheduler scheduler(2, 2); + CTaskScheduler scheduler; // Schedule 5 tasks onto two threads.. Queue the fifth task with high priority. std::atomic_size_t nExecuted = 0; @@ -169,7 +170,7 @@ TEST(TaskSchedulerTest, ConcurrencyAndPrioritizedTaskQueueing) std::this_thread::sleep_for(std::chrono::milliseconds(300)); nExecuted++; - }, (n == 4 ? CTaskScheduler::EScheduleFlags::priority : CTaskScheduler::EScheduleFlags::normal)); + }, (n == 4 ? EScheduleFlags::priority : EScheduleFlags::normal)); bWait = false; // Wait until all threads are finalized diff --git a/tests/unit_tests/sdv_control/includes.h b/tests/unit_tests/sdv_control/includes.h index 6682f15..a5d79c4 100644 --- a/tests/unit_tests/sdv_control/includes.h +++ b/tests/unit_tests/sdv_control/includes.h @@ -36,13 +36,13 @@ public: m_uiInstanceID(uiInstanceID) { // Initialize system - std::string ssStartup = R"code( + std::string ssStartup = R"toml( [LogHandler] ViewFilter = "Fatal" [Application] Mode = "Maintenance" -)code"; +)toml"; if (uiInstanceID) ssStartup += "Instance = " + std::to_string(uiInstanceID) + "\n"; Startup(ssStartup); diff --git a/tests/unit_tests/shared_mem/app_connect.cpp b/tests/unit_tests/shared_mem/app_connect.cpp index ed72b5e..7e2e62d 100644 --- a/tests/unit_tests/shared_mem/app_connect.cpp +++ b/tests/unit_tests/shared_mem/app_connect.cpp @@ -42,7 +42,7 @@ public: */ CReceiver() { - m_threadSender = std::thread(&CReceiver::SendThreadFunc, this); + m_threadSender = sdv::core::secure_thread(&CReceiver::SendThreadFunc, this); } /** @@ -236,7 +236,7 @@ private: std::queue>> m_queueSendData; ///< Queue for sending data. std::condition_variable m_cvDisconnect; ///< Disconnect event. std::condition_variable m_cvReceived; ///< Receive event. - std::thread m_threadSender; ///< Thread to send data. + sdv::core::secure_thread m_threadSender; ///< Thread to send data. std::atomic_bool m_bConnected = false; ///< Set when connected was triggered. std::atomic_bool m_bDisconnect = false; ///< Set when shutdown was triggered. std::atomic_bool m_bShutdown = false; ///< Set when shutdown is processed. @@ -320,9 +320,9 @@ extern "C" int main(int argc, char* argv[]) TRACE("Forced termination of app ", bServer ? "server" : "client", " process is ", bForceTerminate ? "enabled" : "disabled"); TRACE("Long life of app ",bServer ? "server" : "client", " process is ", bLongLife ? "enabled" : "disabled"); - // Create an control management channel (if required). + // Create a control management channel (if required). CSharedMemChannelMgnt mgntControlMgntChannel; - mgntControlMgntChannel.Initialize(""); + mgntControlMgntChannel.Initialize(sdv::SObjectInfo()); if (mgntControlMgntChannel.GetObjectState() != sdv::EObjectState::initialized) return -11; mgntControlMgntChannel.SetOperationMode(sdv::EOperationMode::running); if (mgntControlMgntChannel.GetObjectState() != sdv::EObjectState::running) return -11; @@ -349,7 +349,7 @@ extern "C" int main(int argc, char* argv[]) // Create the data management channel. CSharedMemChannelMgnt mgntDataMgntChannel; - mgntDataMgntChannel.Initialize(""); + mgntDataMgntChannel.Initialize(sdv::SObjectInfo()); if (mgntDataMgntChannel.GetObjectState() != sdv::EObjectState::initialized) return -1; mgntDataMgntChannel.SetOperationMode(sdv::EOperationMode::running); if (mgntDataMgntChannel.GetObjectState() != sdv::EObjectState::running) return -1; @@ -360,10 +360,10 @@ extern "C" int main(int argc, char* argv[]) if (bServer) { TRACE("Server: Create data endpoint..."); - sdv::ipc::SChannelEndpoint sEndpoint = mgntDataMgntChannel.CreateEndpoint(R"code( + sdv::ipc::SChannelEndpoint sEndpoint = mgntDataMgntChannel.CreateEndpoint(R"toml( [IpcChannel] Size = 1024000 -)code"); +)toml"); ptrDataConnection = sEndpoint.pConnection; sdv::pointer ptrConnectInfoData; ptrConnectInfoData.resize(sEndpoint.ssConnectString.size()); diff --git a/tests/unit_tests/shared_mem/in_process_mem_buffer_tests.cpp b/tests/unit_tests/shared_mem/in_process_mem_buffer_tests.cpp index abc282b..aea7350 100644 --- a/tests/unit_tests/shared_mem/in_process_mem_buffer_tests.cpp +++ b/tests/unit_tests/shared_mem/in_process_mem_buffer_tests.cpp @@ -59,7 +59,7 @@ TEST(InProcessMemoryBufferTest, TriggerTestRx) }; std::unique_lock lockStart(mtxStart); - std::thread thread(fnWaitForTrigger); + sdv::core::secure_thread thread(fnWaitForTrigger); cvStart.wait(lockStart); for (size_t n = 0; n < 20; n++) @@ -108,7 +108,7 @@ TEST(InProcessMemoryBufferTest, TriggerTestTx) }; std::unique_lock lockStart(mtxStart); - std::thread thread(fnWaitForTrigger); + sdv::core::secure_thread thread(fnWaitForTrigger); cvStart.wait(lockStart); for (size_t n = 0; n < 20; n++) @@ -170,8 +170,8 @@ TEST(InProcessMemoryBufferTest, TriggerTestRxTx) std::unique_lock lockStartSender(mtxSenderStart); std::unique_lock lockStartReceiver(mtxReceiverStart); - std::thread threadSender(fnWaitForTriggerSender); - std::thread threadReceiver(fnWaitForTriggerReceiver); + sdv::core::secure_thread threadSender(fnWaitForTriggerSender); + sdv::core::secure_thread threadReceiver(fnWaitForTriggerReceiver); cvSenderStart.wait(lockStartSender); lockStartSender.unlock(); cvReceiverStart.wait(lockStartReceiver); @@ -340,8 +340,8 @@ TEST(InProcessMemoryBufferTest, ReserveCommitAccessReleaseNonChronologicalOrder) // Reserve buffers for strings // The buffer header has 16 bytes - // Each allocation is 8 bytes header, 5 bytes data and 3 bytes alignment - CAccessorTxPacket rgTxPackets[32] = {}; + // Each allocation is 8 bytes header, 5 bytes data and 3 bytes alignment + CAccessorTxPacket rgTxPackets[32] = {}; for (int32_t iIndex = 0; iIndex < 15; iIndex++) { auto optTxPacket = sender.Reserve(5); @@ -373,8 +373,8 @@ TEST(InProcessMemoryBufferTest, ReserveCommitAccessReleaseNonChronologicalOrder) { // The text should contain the number 0 EXPECT_TRUE(optRxPacket); - EXPECT_NE(optRxPacket->GetData(), nullptr); - EXPECT_EQ(std::to_string(0), optRxPacket->GetData()); + EXPECT_NE(optRxPacket->GetData(), nullptr); + EXPECT_EQ(std::to_string(0), optRxPacket->GetData()); optRxPacket->Accept(); } else @@ -414,7 +414,7 @@ TEST(InProcessMemoryBufferTest, ReserveCommitAccessReleaseNonChronologicalOrder) EXPECT_NE(rgRxPackets[iIndex].GetData(), nullptr); EXPECT_NE(rgRxPackets[iIndex].GetSize(), 0u); } - if (rgRxPackets[iIndex]) + if (rgRxPackets[iIndex]) { EXPECT_EQ(std::to_string(iIndex), rgRxPackets[iIndex].GetData()); } @@ -442,33 +442,33 @@ TEST(InProcessMemoryBufferTest, SendReceivePattern) ASSERT_TRUE(appcontrol.Startup("")); CInProcMemBufferTx sender; - EXPECT_TRUE(sender.IsValid()); + EXPECT_TRUE(sender.IsValid()); - CInProcMemBufferRx receiver(sender.GetConnectionString()); - EXPECT_TRUE(receiver.IsValid()); + CInProcMemBufferRx receiver(sender.GetConnectionString()); + EXPECT_TRUE(receiver.IsValid()); - CPatternReceiver pattern_inspector(receiver); + CPatternReceiver pattern_inspector(receiver); CPatternSender pattern_generator(sender); // Wait for 2 seconds - std::this_thread::sleep_for(std::chrono::seconds(PATTERN_TEST_TIME_S)); + std::this_thread::sleep_for(std::chrono::seconds(PATTERN_TEST_TIME_S)); // Shutdown pattern_generator.Shutdown(); - pattern_inspector.Shutdown(); + pattern_inspector.Shutdown(); std::cout << "Pattern generator: " << pattern_generator.GetCycleCnt() << " cyles, " << pattern_generator.GetPacketCnt() - << " packets, " << pattern_generator.GetByteCnt() << " bytes" << std::endl; - std::cout << "Pattern inspector: " << pattern_inspector.GetCycleCnt() << " cyles, " << pattern_inspector.GetPacketCnt() + << " packets, " << pattern_generator.GetByteCnt() << " bytes" << std::endl; + std::cout << "Pattern inspector: " << pattern_inspector.GetCycleCnt() << " cyles, " << pattern_inspector.GetPacketCnt() << " packets, " << pattern_inspector.GetByteCnt() << " bytes, " << pattern_inspector.GetErrorCnt() << " errors, " << std::endl; - EXPECT_NE(pattern_generator.GetCycleCnt(), 0u); - EXPECT_NE(pattern_generator.GetPacketCnt(), 0u); - EXPECT_NE(pattern_generator.GetByteCnt(), 0ull); - EXPECT_NE(pattern_inspector.GetCycleCnt(), 0u); - EXPECT_EQ(pattern_inspector.GetErrorCnt(), 0u); - EXPECT_NE(pattern_inspector.GetPacketCnt(), 0u); - EXPECT_NE(pattern_inspector.GetByteCnt(), 0ull); + EXPECT_NE(pattern_generator.GetCycleCnt(), 0u); + EXPECT_NE(pattern_generator.GetPacketCnt(), 0u); + EXPECT_NE(pattern_generator.GetByteCnt(), 0ull); + EXPECT_NE(pattern_inspector.GetCycleCnt(), 0u); + EXPECT_EQ(pattern_inspector.GetErrorCnt(), 0u); + EXPECT_NE(pattern_inspector.GetPacketCnt(), 0u); + EXPECT_NE(pattern_inspector.GetByteCnt(), 0ull); } TEST(InProcessMemoryBufferTest, DelayedSendReceivePattern) @@ -477,33 +477,33 @@ TEST(InProcessMemoryBufferTest, DelayedSendReceivePattern) ASSERT_TRUE(appcontrol.Startup("")); CInProcMemBufferTx sender; - EXPECT_TRUE(sender.IsValid()); + EXPECT_TRUE(sender.IsValid()); - CInProcMemBufferRx receiver(sender.GetConnectionString()); - EXPECT_TRUE(receiver.IsValid()); + CInProcMemBufferRx receiver(sender.GetConnectionString()); + EXPECT_TRUE(receiver.IsValid()); - CPatternReceiver pattern_inspector(receiver); - CPatternSender pattern_generator(sender, 10); + CPatternReceiver pattern_inspector(receiver); + CPatternSender pattern_generator(sender, 10); - // Wait for 2 seconds - std::this_thread::sleep_for(std::chrono::seconds(PATTERN_TEST_TIME_S)); + // Wait for 2 seconds + std::this_thread::sleep_for(std::chrono::seconds(PATTERN_TEST_TIME_S)); - // Shutdown - pattern_generator.Shutdown(); - pattern_inspector.Shutdown(); + // Shutdown + pattern_generator.Shutdown(); + pattern_inspector.Shutdown(); std::cout << "Pattern generator: " << pattern_generator.GetCycleCnt() << " cyles, " << pattern_generator.GetPacketCnt() - << " packets, " << pattern_generator.GetByteCnt() << " bytes" << std::endl; - std::cout << "Pattern inspector: " << pattern_inspector.GetCycleCnt() << " cyles, " << pattern_inspector.GetPacketCnt() + << " packets, " << pattern_generator.GetByteCnt() << " bytes" << std::endl; + std::cout << "Pattern inspector: " << pattern_inspector.GetCycleCnt() << " cyles, " << pattern_inspector.GetPacketCnt() << " packets, " << pattern_inspector.GetByteCnt() << " bytes, " << pattern_inspector.GetErrorCnt() << " errors, " << std::endl; - EXPECT_NE(pattern_generator.GetCycleCnt(), 0u); - EXPECT_NE(pattern_generator.GetPacketCnt(), 0u); - EXPECT_NE(pattern_generator.GetByteCnt(), 0ull); - EXPECT_NE(pattern_inspector.GetCycleCnt(), 0u); - EXPECT_EQ(pattern_inspector.GetErrorCnt(), 0u); - EXPECT_NE(pattern_inspector.GetPacketCnt(), 0u); - EXPECT_NE(pattern_inspector.GetByteCnt(), 0ull); + EXPECT_NE(pattern_generator.GetCycleCnt(), 0u); + EXPECT_NE(pattern_generator.GetPacketCnt(), 0u); + EXPECT_NE(pattern_generator.GetByteCnt(), 0ull); + EXPECT_NE(pattern_inspector.GetCycleCnt(), 0u); + EXPECT_EQ(pattern_inspector.GetErrorCnt(), 0u); + EXPECT_NE(pattern_inspector.GetPacketCnt(), 0u); + EXPECT_NE(pattern_inspector.GetByteCnt(), 0ull); } TEST(InProcessMemoryBufferTest, SendDelayedReceivePattern) @@ -512,33 +512,33 @@ TEST(InProcessMemoryBufferTest, SendDelayedReceivePattern) ASSERT_TRUE(appcontrol.Startup("")); CInProcMemBufferTx sender; - EXPECT_TRUE(sender.IsValid()); + EXPECT_TRUE(sender.IsValid()); - CInProcMemBufferRx receiver(sender.GetConnectionString()); - EXPECT_TRUE(receiver.IsValid()); + CInProcMemBufferRx receiver(sender.GetConnectionString()); + EXPECT_TRUE(receiver.IsValid()); - CPatternReceiver pattern_inspector(receiver, 10); - CPatternSender pattern_generator(sender); + CPatternReceiver pattern_inspector(receiver, 10); + CPatternSender pattern_generator(sender); - // Wait for 2 seconds - std::this_thread::sleep_for(std::chrono::seconds(PATTERN_TEST_TIME_S)); + // Wait for 2 seconds + std::this_thread::sleep_for(std::chrono::seconds(PATTERN_TEST_TIME_S)); - // Shutdown - pattern_generator.Shutdown(); - pattern_inspector.Shutdown(); + // Shutdown + pattern_generator.Shutdown(); + pattern_inspector.Shutdown(); std::cout << "Pattern generator: " << pattern_generator.GetCycleCnt() << " cyles, " << pattern_generator.GetPacketCnt() - << " packets, " << pattern_generator.GetByteCnt() << " bytes" << std::endl; - std::cout << "Pattern inspector: " << pattern_inspector.GetCycleCnt() << " cyles, " << pattern_inspector.GetPacketCnt() + << " packets, " << pattern_generator.GetByteCnt() << " bytes" << std::endl; + std::cout << "Pattern inspector: " << pattern_inspector.GetCycleCnt() << " cyles, " << pattern_inspector.GetPacketCnt() << " packets, " << pattern_inspector.GetByteCnt() << " bytes, " << pattern_inspector.GetErrorCnt() << " errors, " << std::endl; - EXPECT_NE(pattern_generator.GetCycleCnt(), 0u); - EXPECT_NE(pattern_generator.GetPacketCnt(), 0u); - EXPECT_NE(pattern_generator.GetByteCnt(), 0ull); - EXPECT_NE(pattern_inspector.GetCycleCnt(), 0u); - EXPECT_EQ(pattern_inspector.GetErrorCnt(), 0u); - EXPECT_NE(pattern_inspector.GetPacketCnt(), 0u); - EXPECT_NE(pattern_inspector.GetByteCnt(), 0ull); + EXPECT_NE(pattern_generator.GetCycleCnt(), 0u); + EXPECT_NE(pattern_generator.GetPacketCnt(), 0u); + EXPECT_NE(pattern_generator.GetByteCnt(), 0ull); + EXPECT_NE(pattern_inspector.GetCycleCnt(), 0u); + EXPECT_EQ(pattern_inspector.GetErrorCnt(), 0u); + EXPECT_NE(pattern_inspector.GetPacketCnt(), 0u); + EXPECT_NE(pattern_inspector.GetByteCnt(), 0ull); } TEST(InProcessMemoryBufferTest, SendRepeatReceivePattern) @@ -547,50 +547,50 @@ TEST(InProcessMemoryBufferTest, SendRepeatReceivePattern) ASSERT_TRUE(appcontrol.Startup("")); // The first process creates a sender and receiver - CInProcMemBufferTx bufferTX; - EXPECT_TRUE(bufferTX.IsValid()); - CInProcMemBufferRx bufferRX; - EXPECT_TRUE(bufferRX.IsValid()); + CInProcMemBufferTx bufferTX; + EXPECT_TRUE(bufferTX.IsValid()); + CInProcMemBufferRx bufferRX; + EXPECT_TRUE(bufferRX.IsValid()); // The connection string containing the RX and TX strings for the repeater std::string ssConnectionString = bufferTX.GetConnectionString() + "\n" + bufferRX.GetConnectionString(); // The repeater process creates a receiver and sender - CInProcMemBufferRx bufferRepeaterRX(ssConnectionString); - EXPECT_TRUE(bufferRepeaterRX.IsValid()); - CInProcMemBufferTx bufferRepeaterTX(ssConnectionString); - EXPECT_TRUE(bufferRepeaterTX.IsValid()); + CInProcMemBufferRx bufferRepeaterRX(ssConnectionString); + EXPECT_TRUE(bufferRepeaterRX.IsValid()); + CInProcMemBufferTx bufferRepeaterTX(ssConnectionString); + EXPECT_TRUE(bufferRepeaterTX.IsValid()); // Connect the pattern generator and inspector CPatternReceiver pattern_inspector(bufferRX); - CPatternRepeater pattern_repeater(bufferRepeaterRX, bufferRepeaterTX); - CPatternSender pattern_generator(bufferTX); + CPatternRepeater pattern_repeater(bufferRepeaterRX, bufferRepeaterTX); + CPatternSender pattern_generator(bufferTX); - // Wait for 2 seconds - std::this_thread::sleep_for(std::chrono::seconds(PATTERN_TEST_TIME_S)); + // Wait for 2 seconds + std::this_thread::sleep_for(std::chrono::seconds(PATTERN_TEST_TIME_S)); - // Shutdown - pattern_generator.Shutdown(); - pattern_repeater.Shutdown(); - pattern_inspector.Shutdown(); + // Shutdown + pattern_generator.Shutdown(); + pattern_repeater.Shutdown(); + pattern_inspector.Shutdown(); std::cout << "Pattern generator: " << pattern_generator.GetCycleCnt() << " cyles, " << pattern_generator.GetPacketCnt() - << " packets, " << pattern_generator.GetByteCnt() << " bytes" << std::endl; - std::cout << "Pattern inspector: " << pattern_inspector.GetCycleCnt() << " cyles, " << pattern_inspector.GetPacketCnt() + << " packets, " << pattern_generator.GetByteCnt() << " bytes" << std::endl; + std::cout << "Pattern inspector: " << pattern_inspector.GetCycleCnt() << " cyles, " << pattern_inspector.GetPacketCnt() << " packets, " << pattern_inspector.GetByteCnt() << " bytes, " << pattern_inspector.GetErrorCnt() << " errors, " << std::endl; - std::cout << "Pattern repeater: " << pattern_repeater.GetCycleCnt() << " cyles, " << pattern_repeater.GetPacketCnt() + std::cout << "Pattern repeater: " << pattern_repeater.GetCycleCnt() << " cyles, " << pattern_repeater.GetPacketCnt() << " packets, " << pattern_repeater.GetByteCnt() << " bytes, " << pattern_repeater.GetErrorCnt() << " errors, " << std::endl; - EXPECT_NE(pattern_generator.GetCycleCnt(), 0u); - EXPECT_NE(pattern_generator.GetPacketCnt(), 0u); - EXPECT_NE(pattern_generator.GetByteCnt(), 0ull); - EXPECT_NE(pattern_inspector.GetCycleCnt(), 0u); - EXPECT_EQ(pattern_inspector.GetErrorCnt(), 0u); - EXPECT_NE(pattern_inspector.GetPacketCnt(), 0u); - EXPECT_NE(pattern_inspector.GetByteCnt(), 0ull); - EXPECT_NE(pattern_repeater.GetCycleCnt(), 0u); - EXPECT_EQ(pattern_repeater.GetErrorCnt(), 0u); - EXPECT_NE(pattern_repeater.GetPacketCnt(), 0u); - EXPECT_NE(pattern_repeater.GetByteCnt(), 0ull); + EXPECT_NE(pattern_generator.GetCycleCnt(), 0u); + EXPECT_NE(pattern_generator.GetPacketCnt(), 0u); + EXPECT_NE(pattern_generator.GetByteCnt(), 0ull); + EXPECT_NE(pattern_inspector.GetCycleCnt(), 0u); + EXPECT_EQ(pattern_inspector.GetErrorCnt(), 0u); + EXPECT_NE(pattern_inspector.GetPacketCnt(), 0u); + EXPECT_NE(pattern_inspector.GetByteCnt(), 0ull); + EXPECT_NE(pattern_repeater.GetCycleCnt(), 0u); + EXPECT_EQ(pattern_repeater.GetErrorCnt(), 0u); + EXPECT_NE(pattern_repeater.GetPacketCnt(), 0u); + EXPECT_NE(pattern_repeater.GetByteCnt(), 0ull); } diff --git a/tests/unit_tests/shared_mem/pattern_gen.cpp b/tests/unit_tests/shared_mem/pattern_gen.cpp index 7a9aa0a..1258852 100644 --- a/tests/unit_tests/shared_mem/pattern_gen.cpp +++ b/tests/unit_tests/shared_mem/pattern_gen.cpp @@ -24,7 +24,7 @@ CPatternSender::CPatternSender(CMemBufferAccessorTx& raccessorOut, uint32_t uiDe m_raccessorOut(raccessorOut), m_uiDelayMs(uiDelayMs) { // Start the thread - m_thread = std::thread(&CPatternSender::Process, this); + m_thread = sdv::core::secure_thread(&CPatternSender::Process, this); // Wait for the thread to run while (!m_bStarted) std::this_thread::sleep_for(std::chrono::milliseconds(1)); @@ -67,7 +67,7 @@ CPatternReceiver::CPatternReceiver(CMemBufferAccessorRx& raccessorIn, uint32_t u m_raccessorIn(raccessorIn),m_uiDelayMs(uiDelayMs) { // Start the thread - m_thread = std::thread(& CPatternReceiver::Process, this); + m_thread = sdv::core::secure_thread(& CPatternReceiver::Process, this); // Wait for the thread to run while (!m_bStarted) std::this_thread::sleep_for(std::chrono::milliseconds(1)); @@ -123,7 +123,7 @@ CPatternRepeater::CPatternRepeater(CMemBufferAccessorRx& raccessorIn, CMemBuffer m_raccessorIn(raccessorIn), m_raccessorOut(raccessorOut), m_uiDelayMs(uiDelayMs) { // Start the thread - m_thread = std::thread(&CPatternRepeater::Process, this); + m_thread = sdv::core::secure_thread(&CPatternRepeater::Process, this); // Wait for the thread to run while (!m_bStarted) std::this_thread::sleep_for(std::chrono::milliseconds(1)); diff --git a/tests/unit_tests/shared_mem/pattern_gen.h b/tests/unit_tests/shared_mem/pattern_gen.h index 43d1acd..23a6f69 100644 --- a/tests/unit_tests/shared_mem/pattern_gen.h +++ b/tests/unit_tests/shared_mem/pattern_gen.h @@ -16,6 +16,7 @@ #include #include +#include #include "../../../sdv_services/ipc_shared_mem/mem_buffer_accessor.h" /** @@ -55,14 +56,14 @@ public: */ private: - CMemBufferAccessorTx& m_raccessorOut; //!< Reference to the output accessor - std::thread m_thread; //!< Processing thread - std::atomic_bool m_bStarted = false; //!< Set by the thread when started. - std::atomic_bool m_bShutdown = false; //!< When set, shutdown the thread. - uint32_t m_uiDelayMs = 0u; //!< Delay (in ms) to insert while processing. - uint32_t m_uiCycleCnt = 0u; //!< Amount of packets - uint32_t m_uiPacketCnt = 0u; //!< Amount of packets - uint64_t m_uiByteCnt = 0ull; //!< Amount of bytes + CMemBufferAccessorTx& m_raccessorOut; //!< Reference to the output accessor + sdv::core::secure_thread m_thread; //!< Processing thread + std::atomic_bool m_bStarted = false; //!< Set by the thread when started. + std::atomic_bool m_bShutdown = false; //!< When set, shutdown the thread. + uint32_t m_uiDelayMs = 0u; //!< Delay (in ms) to insert while processing. + uint32_t m_uiCycleCnt = 0u; //!< Amount of packets + uint32_t m_uiPacketCnt = 0u; //!< Amount of packets + uint64_t m_uiByteCnt = 0ull; //!< Amount of bytes }; class CPatternReceiver @@ -98,15 +99,15 @@ public: */ private: - CMemBufferAccessorRx& m_raccessorIn; //!< Reference to the input accessor - std::thread m_thread; //!< Processing thread + CMemBufferAccessorRx& m_raccessorIn; //!< Reference to the input accessor + sdv::core::secure_thread m_thread; //!< Processing thread bool m_bStarted = false; //!< Set by the thread when started. bool m_bShutdown = false; //!< When set, shutdown the thread. - uint32_t m_uiDelayMs = 0u; //!< Delay (in ms) to insert while processing. - uint32_t m_uiCycleCnt = 0u; //!< Amount of packets - uint32_t m_uiErrorCnt = 0u; //!< Amount of counter errors - uint32_t m_uiPacketCnt = 0u; //!< Amount of packets - uint64_t m_uiByteCnt = 0ull; //!< Amount of bytes + uint32_t m_uiDelayMs = 0u; //!< Delay (in ms) to insert while processing. + uint32_t m_uiCycleCnt = 0u; //!< Amount of packets + uint32_t m_uiErrorCnt = 0u; //!< Amount of counter errors + uint32_t m_uiPacketCnt = 0u; //!< Amount of packets + uint64_t m_uiByteCnt = 0ull; //!< Amount of bytes }; class CPatternRepeater @@ -143,16 +144,16 @@ public: */ private: - CMemBufferAccessorRx& m_raccessorIn; //!< Reference to the input accessor - CMemBufferAccessorTx& m_raccessorOut; //!< Reference to the output accessor - std::thread m_thread; //!< Processing thread - bool m_bStarted = false; //!< Set by the thread when started. - bool m_bShutdown = false; //!< When set, shutdown the thread. - uint32_t m_uiDelayMs = 0u; //!< Delay (in ms) to insert while processing. - uint32_t m_uiCycleCnt = 0u; //!< Amount of packets - uint32_t m_uiErrorCnt = 0u; //!< Amount of counter errors - uint32_t m_uiPacketCnt = 0u; //!< Amount of packets - uint64_t m_uiByteCnt = 0ull; //!< Amount of bytes + CMemBufferAccessorRx& m_raccessorIn; //!< Reference to the input accessor + CMemBufferAccessorTx& m_raccessorOut; //!< Reference to the output accessor + sdv::core::secure_thread m_thread; //!< Processing thread + bool m_bStarted = false; //!< Set by the thread when started. + bool m_bShutdown = false; //!< When set, shutdown the thread. + uint32_t m_uiDelayMs = 0u; //!< Delay (in ms) to insert while processing. + uint32_t m_uiCycleCnt = 0u; //!< Amount of packets + uint32_t m_uiErrorCnt = 0u; //!< Amount of counter errors + uint32_t m_uiPacketCnt = 0u; //!< Amount of packets + uint64_t m_uiByteCnt = 0ull; //!< Amount of bytes }; #endif // !defined(PATTERN_GEN_H) \ No newline at end of file diff --git a/tests/unit_tests/shared_mem/shared_mem_buffer_tests.cpp b/tests/unit_tests/shared_mem/shared_mem_buffer_tests.cpp index 5d698f3..58d10bb 100644 --- a/tests/unit_tests/shared_mem/shared_mem_buffer_tests.cpp +++ b/tests/unit_tests/shared_mem/shared_mem_buffer_tests.cpp @@ -88,7 +88,7 @@ TEST(SharedMemoryBufferTest, TriggerTestRx) }; std::unique_lock lockStart(mtxStart); - std::thread thread(fnWaitForTrigger); + sdv::core::secure_thread thread(fnWaitForTrigger); cvStart.wait(lockStart); for (size_t n = 0; n < 20; n++) @@ -141,7 +141,7 @@ TEST(SharedMemoryBufferTest, TriggerTestTx) }; std::unique_lock lockStart(mtxStart); - std::thread thread(fnWaitForTrigger); + sdv::core::secure_thread thread(fnWaitForTrigger); cvStart.wait(lockStart); for (size_t n = 0; n < 20; n++) @@ -208,8 +208,8 @@ TEST(SharedMemoryBufferTest, TriggerTestRxTx) std::unique_lock lockStartSender(mtxSenderStart); std::unique_lock lockStartReceiver(mtxReceiverStart); - std::thread threadSender(fnWaitForTriggerSender); - std::thread threadReceiver(fnWaitForTriggerReceiver); + sdv::core::secure_thread threadSender(fnWaitForTriggerSender); + sdv::core::secure_thread threadReceiver(fnWaitForTriggerReceiver); cvSenderStart.wait(lockStartSender); lockStartSender.unlock(); cvReceiverStart.wait(lockStartReceiver); @@ -693,8 +693,8 @@ TEST(SharedMemoryBufferTest, SendRepeatReceivePattern) TEST(SharedMemoryBufferTest, AppProcessSendRepeatReceivePattern) { sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"code([Application] -Mode = "Essential")code")); + ASSERT_TRUE(appcontrol.Startup(R"toml([Application] +Mode = "Essential")toml")); LoadSupportServices(); CSharedMemBufferTx bufferTX; @@ -747,8 +747,8 @@ Mode = "Essential")code")); TEST(SharedMemoryBufferTest, SendRepeatReceivePatternBetweenTwoAppProcesses) { sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"code([Application] -Mode = "Essential")code")); + ASSERT_TRUE(appcontrol.Startup(R"toml([Application] +Mode = "Essential")toml")); LoadSupportServices(); // test starts 2 app processes, one should be the sender of the pattern, the other is the repeater diff --git a/tests/unit_tests/shared_mem/shared_mem_connect.cpp b/tests/unit_tests/shared_mem/shared_mem_connect.cpp index 6277f2c..cec9b08 100644 --- a/tests/unit_tests/shared_mem/shared_mem_connect.cpp +++ b/tests/unit_tests/shared_mem/shared_mem_connect.cpp @@ -81,7 +81,7 @@ public: // Start the processing thread if needed if (!m_threadDecoupledSend.joinable()) m_threadDecoupledSend = - std::thread(&CConnectReceiver::DecoupledSendThread, this); + sdv::core::secure_thread(&CConnectReceiver::DecoupledSendThread, this); // Store data into the queue for sending. m_queueDecoupledSend.push(std::move(seqData)); @@ -186,7 +186,7 @@ private: bool m_bConnectError = false; ///< Connection error ocurred. bool m_bCommError = false; ///< Communication error occurred. bool m_bForcedDisconnect = false; ///< Force disconnect. - std::thread m_threadDecoupledSend; ///< Decoupled send thread. + sdv::core::secure_thread m_threadDecoupledSend; ///< Decoupled send thread. std::queue>> m_queueDecoupledSend; ///< Data queue for sending. std::condition_variable m_cvDecoupledSend; ///< Trigger decoupled sending. std::atomic_bool m_bShutdown = false; ///< Shutdown send thread. @@ -203,7 +203,7 @@ TEST(SharedMemChannelService, Instantiate) ASSERT_TRUE(appcontrol.Startup("")); CSharedMemChannelMgnt mgnt; - EXPECT_NO_THROW(mgnt.Initialize("")); + EXPECT_NO_THROW(mgnt.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgnt.GetObjectState(), sdv::EObjectState::initialized); @@ -220,7 +220,7 @@ TEST(SharedMemChannelService, ChannelConfigString) ASSERT_TRUE(appcontrol.Startup("")); CSharedMemChannelMgnt mgnt; - EXPECT_NO_THROW(mgnt.Initialize("")); + EXPECT_NO_THROW(mgnt.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgnt.GetObjectState(), sdv::EObjectState::initialized); @@ -237,7 +237,7 @@ TEST(SharedMemChannelService, CreateRandomEndpoint) CSharedMemChannelMgnt mgnt; // Create an endpoint. - EXPECT_NO_THROW(mgnt.Initialize("")); + EXPECT_NO_THROW(mgnt.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgnt.GetObjectState(), sdv::EObjectState::initialized); sdv::ipc::SChannelEndpoint sChannelEndpoint = mgnt.CreateEndpoint(""); EXPECT_NE(sChannelEndpoint.pConnection, nullptr); @@ -256,12 +256,12 @@ TEST(SharedMemChannelService, CreateExplicitEndpoint) CSharedMemChannelMgnt mgnt; // Create an endpoint. - EXPECT_NO_THROW(mgnt.Initialize("")); + EXPECT_NO_THROW(mgnt.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgnt.GetObjectState(), sdv::EObjectState::initialized); - sdv::ipc::SChannelEndpoint sChannelEndpoint = mgnt.CreateEndpoint(R"code([IpcChannel] + sdv::ipc::SChannelEndpoint sChannelEndpoint = mgnt.CreateEndpoint(R"toml([IpcChannel] Name = "CHANNEL_1234" Size = 10240 -)code"); +)toml"); EXPECT_NE(sChannelEndpoint.pConnection, nullptr); EXPECT_FALSE(sChannelEndpoint.ssConnectString.empty()); if (sChannelEndpoint.pConnection) sdv::TObjectPtr(sChannelEndpoint.pConnection); @@ -278,9 +278,9 @@ TEST(SharedMemChannelService, GetRandomEndpointAccess) CSharedMemChannelMgnt mgntServer, mgntClient; // Create an endpoint. - EXPECT_NO_THROW(mgntServer.Initialize("")); + EXPECT_NO_THROW(mgntServer.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgntServer.GetObjectState(), sdv::EObjectState::initialized); - EXPECT_NO_THROW(mgntClient.Initialize("")); + EXPECT_NO_THROW(mgntClient.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgntClient.GetObjectState(), sdv::EObjectState::initialized); sdv::ipc::SChannelEndpoint sChannelEndpoint = mgntServer.CreateEndpoint(""); EXPECT_NE(sChannelEndpoint.pConnection, nullptr); @@ -306,14 +306,14 @@ TEST(SharedMemChannelService, GetExplicitEndpointAccess) CSharedMemChannelMgnt mgntServer, mgntClient; // Create an endpoint. - EXPECT_NO_THROW(mgntServer.Initialize("")); + EXPECT_NO_THROW(mgntServer.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgntServer.GetObjectState(), sdv::EObjectState::initialized); - EXPECT_NO_THROW(mgntClient.Initialize("")); + EXPECT_NO_THROW(mgntClient.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgntClient.GetObjectState(), sdv::EObjectState::initialized); - sdv::ipc::SChannelEndpoint sChannelEndpoint = mgntServer.CreateEndpoint(R"code([IpcChannel] + sdv::ipc::SChannelEndpoint sChannelEndpoint = mgntServer.CreateEndpoint(R"toml([IpcChannel] Name = "CHANNEL_1234" Size = 10240 -)code"); +)toml"); EXPECT_NE(sChannelEndpoint.pConnection, nullptr); EXPECT_FALSE(sChannelEndpoint.ssConnectString.empty()); @@ -337,7 +337,7 @@ TEST(SharedMemChannelService, WaitForConnection) CSharedMemChannelMgnt mgnt; // Create an endpoint. - EXPECT_NO_THROW(mgnt.Initialize("")); + EXPECT_NO_THROW(mgnt.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgnt.GetObjectState(), sdv::EObjectState::initialized); sdv::ipc::SChannelEndpoint sChannelEndpoint = mgnt.CreateEndpoint(""); EXPECT_NE(sChannelEndpoint.pConnection, nullptr); @@ -359,7 +359,7 @@ TEST(SharedMemChannelService, WaitForConnection) pConnection->GetConnectState() == sdv::ipc::EConnectState::connecting); // Wait for connection for infinite period with cancel. - std::thread threadCancelWait([&]() + sdv::core::secure_thread threadCancelWait([&]() { std::this_thread::sleep_for(std::chrono::milliseconds(500)); pConnection->CancelWait(); @@ -384,9 +384,9 @@ TEST(SharedMemChannelService, AsyncConnect) CSharedMemChannelMgnt mgntServer, mgntClient; // Create an endpoint. - EXPECT_NO_THROW(mgntServer.Initialize("")); + EXPECT_NO_THROW(mgntServer.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgntServer.GetObjectState(), sdv::EObjectState::initialized); - EXPECT_NO_THROW(mgntClient.Initialize("")); + EXPECT_NO_THROW(mgntClient.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgntClient.GetObjectState(), sdv::EObjectState::initialized); sdv::ipc::SChannelEndpoint sChannelEndpoint = mgntServer.CreateEndpoint(""); EXPECT_NE(sChannelEndpoint.pConnection, nullptr); @@ -481,9 +481,9 @@ TEST(SharedMemChannelService, EstablishConnectionEvents) CSharedMemChannelMgnt mgntServer, mgntClient; // Create an endpoint. - EXPECT_NO_THROW(mgntServer.Initialize("")); + EXPECT_NO_THROW(mgntServer.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgntServer.GetObjectState(), sdv::EObjectState::initialized); - EXPECT_NO_THROW(mgntClient.Initialize("")); + EXPECT_NO_THROW(mgntClient.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgntClient.GetObjectState(), sdv::EObjectState::initialized); sdv::ipc::SChannelEndpoint sChannelEndpoint = mgntServer.CreateEndpoint(""); EXPECT_NE(sChannelEndpoint.pConnection, nullptr); @@ -597,11 +597,11 @@ TEST(SharedMemChannelService, EstablishReconnect) CSharedMemChannelMgnt mgntServer, mgntClient1, mgntClient2; // Create an endpoint. - EXPECT_NO_THROW(mgntServer.Initialize("")); + EXPECT_NO_THROW(mgntServer.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgntServer.GetObjectState(), sdv::EObjectState::initialized); - EXPECT_NO_THROW(mgntClient1.Initialize("")); + EXPECT_NO_THROW(mgntClient1.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgntClient1.GetObjectState(), sdv::EObjectState::initialized); - EXPECT_NO_THROW(mgntClient2.Initialize("")); + EXPECT_NO_THROW(mgntClient2.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgntClient2.GetObjectState(), sdv::EObjectState::initialized); sdv::ipc::SChannelEndpoint sChannelEndpoint = mgntServer.CreateEndpoint(""); EXPECT_NE(sChannelEndpoint.pConnection, nullptr); @@ -680,11 +680,11 @@ TEST(SharedMemChannelService, EstablishReconnectEvents) CSharedMemChannelMgnt mgntServer, mgntClient1, mgntClient2; // Create an endpoint. - EXPECT_NO_THROW(mgntServer.Initialize("")); + EXPECT_NO_THROW(mgntServer.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgntServer.GetObjectState(), sdv::EObjectState::initialized); - EXPECT_NO_THROW(mgntClient1.Initialize("")); + EXPECT_NO_THROW(mgntClient1.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgntClient1.GetObjectState(), sdv::EObjectState::initialized); - EXPECT_NO_THROW(mgntClient2.Initialize("")); + EXPECT_NO_THROW(mgntClient2.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgntClient2.GetObjectState(), sdv::EObjectState::initialized); sdv::ipc::SChannelEndpoint sChannelEndpoint = mgntServer.CreateEndpoint(""); EXPECT_NE(sChannelEndpoint.pConnection, nullptr); @@ -779,14 +779,14 @@ TEST(SharedMemChannelService, EstablishReconnectEvents) TEST(SharedMemChannelService, AppEstablishConnection) { sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"code([Application] -Mode = "Essential")code")); + ASSERT_TRUE(appcontrol.Startup(R"toml([Application] +Mode = "Essential")toml")); LoadSupportServices(); CSharedMemChannelMgnt mgntServer; // Create an endpoint. - EXPECT_NO_THROW(mgntServer.Initialize("")); + EXPECT_NO_THROW(mgntServer.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgntServer.GetObjectState(), sdv::EObjectState::initialized); sdv::ipc::SChannelEndpoint sChannelEndpoint = mgntServer.CreateEndpoint(""); EXPECT_NE(sChannelEndpoint.pConnection, nullptr); @@ -860,14 +860,14 @@ Mode = "Essential")code")); TEST(SharedMemChannelService, AppGracefullyShutdown) { sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"code([Application] -Mode = "Essential")code")); + ASSERT_TRUE(appcontrol.Startup(R"toml([Application] +Mode = "Essential")toml")); LoadSupportServices(); CSharedMemChannelMgnt mgntServer; // Create an endpoint. - EXPECT_NO_THROW(mgntServer.Initialize("")); + EXPECT_NO_THROW(mgntServer.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgntServer.GetObjectState(), sdv::EObjectState::initialized); sdv::ipc::SChannelEndpoint sChannelEndpoint = mgntServer.CreateEndpoint(""); EXPECT_NE(sChannelEndpoint.pConnection, nullptr); @@ -937,14 +937,14 @@ Mode = "Essential")code")); TEST(SharedMemChannelService, AppForcedShutdown_Watchdog) { sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"code([Application] -Mode = "Essential")code")); + ASSERT_TRUE(appcontrol.Startup(R"toml([Application] +Mode = "Essential")toml")); LoadSupportServices(); CSharedMemChannelMgnt mgntServer; // Create an endpoint. - EXPECT_NO_THROW(mgntServer.Initialize("")); + EXPECT_NO_THROW(mgntServer.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgntServer.GetObjectState(), sdv::EObjectState::initialized); sdv::ipc::SChannelEndpoint sChannelEndpoint = mgntServer.CreateEndpoint(""); EXPECT_NE(sChannelEndpoint.pConnection, nullptr); @@ -1019,8 +1019,8 @@ Mode = "Essential")code")); TEST(SharedMemChannelService, IndirectAppGracefullyShutdown) { sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"code([Application] -Mode = "Essential")code")); + ASSERT_TRUE(appcontrol.Startup(R"toml([Application] +Mode = "Essential")toml")); LoadSupportServices(); sdv::process::IProcessControl* pProcessControl = sdv::core::GetObject("ProcessControlService"); @@ -1030,7 +1030,7 @@ Mode = "Essential")code")); // Create the first control endpoint. CSharedMemChannelMgnt mgntControl1; - EXPECT_NO_THROW(mgntControl1.Initialize("")); + EXPECT_NO_THROW(mgntControl1.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgntControl1.GetObjectState(), sdv::EObjectState::initialized); sdv::ipc::SChannelEndpoint sEndpoint1 = mgntControl1.CreateEndpoint(""); EXPECT_NE(sEndpoint1.pConnection, nullptr); @@ -1066,7 +1066,7 @@ Mode = "Essential")code")); // Create the second control endpoint. CSharedMemChannelMgnt mgntControl2; - EXPECT_NO_THROW(mgntControl2.Initialize("")); + EXPECT_NO_THROW(mgntControl2.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgntControl2.GetObjectState(), sdv::EObjectState::initialized); sdv::ipc::SChannelEndpoint sEndpoint2 = mgntControl2.CreateEndpoint(""); EXPECT_NE(sEndpoint2.pConnection, nullptr); @@ -1100,8 +1100,8 @@ Mode = "Essential")code")); TEST(SharedMemChannelService, IndirectAppServerForceShutdown_Watchdog) { sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"code([Application] -Mode = "Essential")code")); + ASSERT_TRUE(appcontrol.Startup(R"toml([Application] +Mode = "Essential")toml")); LoadSupportServices(); sdv::process::IProcessControl* pProcessControl = sdv::core::GetObject("ProcessControlService"); @@ -1113,7 +1113,7 @@ Mode = "Essential")code")); // Create the first control endpoint. CSharedMemChannelMgnt mgntControl1; - EXPECT_NO_THROW(mgntControl1.Initialize("")); + EXPECT_NO_THROW(mgntControl1.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgntControl1.GetObjectState(), sdv::EObjectState::initialized); sdv::ipc::SChannelEndpoint sEndpoint1 = mgntControl1.CreateEndpoint(""); EXPECT_NE(sEndpoint1.pConnection, nullptr); @@ -1149,7 +1149,7 @@ Mode = "Essential")code")); // Create the second control endpoint. CSharedMemChannelMgnt mgntControl2; - EXPECT_NO_THROW(mgntControl2.Initialize("")); + EXPECT_NO_THROW(mgntControl2.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgntControl2.GetObjectState(), sdv::EObjectState::initialized); sdv::ipc::SChannelEndpoint sEndpoint2 = mgntControl2.CreateEndpoint(""); EXPECT_NE(sEndpoint2.pConnection, nullptr); @@ -1183,8 +1183,8 @@ Mode = "Essential")code")); TEST(SharedMemChannelService, IndirectAppClientForceShutdown_Watchdog) { sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"code([Application] -Mode = "Essential")code")); + ASSERT_TRUE(appcontrol.Startup(R"toml([Application] +Mode = "Essential")toml")); LoadSupportServices(); sdv::process::IProcessControl* pProcessControl = sdv::core::GetObject("ProcessControlService"); @@ -1194,7 +1194,7 @@ Mode = "Essential")code")); // Create the first control endpoint. CSharedMemChannelMgnt mgntControl1; - EXPECT_NO_THROW(mgntControl1.Initialize("")); + EXPECT_NO_THROW(mgntControl1.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgntControl1.GetObjectState(), sdv::EObjectState::initialized); sdv::ipc::SChannelEndpoint sEndpoint1 = mgntControl1.CreateEndpoint(""); EXPECT_NE(sEndpoint1.pConnection, nullptr); @@ -1230,7 +1230,7 @@ Mode = "Essential")code")); // Create the second control endpoint. CSharedMemChannelMgnt mgntControl2; - EXPECT_NO_THROW(mgntControl2.Initialize("")); + EXPECT_NO_THROW(mgntControl2.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgntControl2.GetObjectState(), sdv::EObjectState::initialized); sdv::ipc::SChannelEndpoint sEndpoint2 = mgntControl2.CreateEndpoint(""); EXPECT_NE(sEndpoint2.pConnection, nullptr); diff --git a/tests/unit_tests/shared_mem/shared_mem_large_data_tests.cpp b/tests/unit_tests/shared_mem/shared_mem_large_data_tests.cpp index 946eab1..39b999f 100644 --- a/tests/unit_tests/shared_mem/shared_mem_large_data_tests.cpp +++ b/tests/unit_tests/shared_mem/shared_mem_large_data_tests.cpp @@ -90,7 +90,7 @@ public: // Start the processing thread if needed if (!m_threadDecoupledSend.joinable()) - m_threadDecoupledSend = std::thread(&CLargeDataReceiver::DecoupledSendThread, this); + m_threadDecoupledSend = sdv::core::secure_thread(&CLargeDataReceiver::DecoupledSendThread, this); // Store data into the queue for sending. m_queueDecoupledSend.push(std::move(seqData)); @@ -262,7 +262,7 @@ private: bool m_bForcedDisconnect = false; ///< Force disconnect. std::atomic_size_t m_nCount = 0; ///< Receive counter. std::condition_variable m_cvReceived; ///< Receive event. - std::thread m_threadDecoupledSend; ///< Decoupled send thread. + sdv::core::secure_thread m_threadDecoupledSend; ///< Decoupled send thread. std::queue>> m_queueDecoupledSend; ///< Data queue for sending. std::condition_variable m_cvDecoupledSend; ///< Trigger decoupled sending. std::atomic_bool m_bShutdown = false; ///< Shutdown send thread. @@ -271,27 +271,29 @@ private: TEST(SharedMemChannelService, CommunicateOneLargeBlock) { sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"code( + ASSERT_TRUE(appcontrol.Startup(R"toml( [Console] Report = "Silent" - )code")); + )toml")); appcontrol.SetConfigMode(); CSharedMemChannelMgnt mgntServer, mgntClient; // Create an endpoint. - EXPECT_NO_THROW(mgntServer.Initialize("")); + EXPECT_NO_THROW(mgntServer.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgntServer.GetObjectState(), sdv::EObjectState::initialized); mgntServer.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(mgntServer.GetObjectState(), sdv::EObjectState::running); - EXPECT_NO_THROW(mgntClient.Initialize("service = \"client\"")); + sdv::SObjectInfo sClientInfo{}; + sClientInfo.ssConfig = "service = \"client\""; + EXPECT_NO_THROW(mgntClient.Initialize(sClientInfo)); EXPECT_EQ(mgntClient.GetObjectState(), sdv::EObjectState::initialized); mgntClient.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(mgntClient.GetObjectState(), sdv::EObjectState::running); - sdv::ipc::SChannelEndpoint sChannelEndpoint = mgntServer.CreateEndpoint(R"code( + sdv::ipc::SChannelEndpoint sChannelEndpoint = mgntServer.CreateEndpoint(R"toml( [IpcChannel] Size = 1024000 -)code"); +)toml"); EXPECT_NE(sChannelEndpoint.pConnection, nullptr); EXPECT_FALSE(sChannelEndpoint.ssConnectString.empty()); @@ -376,18 +378,20 @@ TEST(SharedMemChannelService, CommunicateMultiLargeBlock) CSharedMemChannelMgnt mgntServer, mgntClient; // Create an endpoint. - EXPECT_NO_THROW(mgntServer.Initialize("")); + EXPECT_NO_THROW(mgntServer.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgntServer.GetObjectState(), sdv::EObjectState::initialized); mgntServer.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(mgntServer.GetObjectState(), sdv::EObjectState::running); - EXPECT_NO_THROW(mgntClient.Initialize("service = \"client\"")); + sdv::SObjectInfo sClientInfo{}; + sClientInfo.ssConfig = "service = \"client\""; + EXPECT_NO_THROW(mgntClient.Initialize(sClientInfo)); EXPECT_EQ(mgntClient.GetObjectState(), sdv::EObjectState::initialized); mgntClient.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(mgntClient.GetObjectState(), sdv::EObjectState::running); - sdv::ipc::SChannelEndpoint sChannelEndpoint = mgntServer.CreateEndpoint(R"code( + sdv::ipc::SChannelEndpoint sChannelEndpoint = mgntServer.CreateEndpoint(R"toml( [IpcChannel] Size = 1024000 -)code"); +)toml"); EXPECT_NE(sChannelEndpoint.pConnection, nullptr); EXPECT_FALSE(sChannelEndpoint.ssConnectString.empty()); @@ -504,18 +508,20 @@ TEST(SharedMemChannelService, CommunicateFragmentedLargeBlock) CSharedMemChannelMgnt mgntServer, mgntClient; // Create an endpoint. - EXPECT_NO_THROW(mgntServer.Initialize("")); + EXPECT_NO_THROW(mgntServer.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgntServer.GetObjectState(), sdv::EObjectState::initialized); mgntServer.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(mgntServer.GetObjectState(), sdv::EObjectState::running); - EXPECT_NO_THROW(mgntClient.Initialize("service = \"client\"")); + sdv::SObjectInfo sClientInfo{}; + sClientInfo.ssConfig = "service = \"client\""; + EXPECT_NO_THROW(mgntClient.Initialize(sClientInfo)); EXPECT_EQ(mgntClient.GetObjectState(), sdv::EObjectState::initialized); mgntClient.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(mgntClient.GetObjectState(), sdv::EObjectState::running); - sdv::ipc::SChannelEndpoint sChannelEndpoint = mgntServer.CreateEndpoint(R"code( + sdv::ipc::SChannelEndpoint sChannelEndpoint = mgntServer.CreateEndpoint(R"toml( [IpcChannel] Size = 1024000 -)code"); +)toml"); EXPECT_NE(sChannelEndpoint.pConnection, nullptr); EXPECT_FALSE(sChannelEndpoint.ssConnectString.empty()); @@ -614,23 +620,23 @@ Size = 1024000 TEST(SharedMemChannelService, AppCommunicateOneLargeBlock) { sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"code( + ASSERT_TRUE(appcontrol.Startup(R"toml( [Application] -Mode="Essential")code")); +Mode="Essential")toml")); LoadSupportServices(); appcontrol.SetConfigMode(); CSharedMemChannelMgnt mgntServer; // Create an endpoint. - EXPECT_NO_THROW(mgntServer.Initialize("")); + EXPECT_NO_THROW(mgntServer.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgntServer.GetObjectState(), sdv::EObjectState::initialized); mgntServer.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(mgntServer.GetObjectState(), sdv::EObjectState::running); - sdv::ipc::SChannelEndpoint sChannelEndpoint = mgntServer.CreateEndpoint(R"code( + sdv::ipc::SChannelEndpoint sChannelEndpoint = mgntServer.CreateEndpoint(R"toml( [IpcChannel] Size = 1024000 -)code"); +)toml"); EXPECT_NE(sChannelEndpoint.pConnection, nullptr); EXPECT_FALSE(sChannelEndpoint.ssConnectString.empty()); @@ -698,23 +704,23 @@ Size = 1024000 TEST(SharedMemChannelService, AppCommunicateMultiLargeBlock) { sdv::app::CAppControl appcontrol; - ASSERT_TRUE(appcontrol.Startup(R"code( + ASSERT_TRUE(appcontrol.Startup(R"toml( [Application] -Mode="Essential")code")); +Mode="Essential")toml")); LoadSupportServices(); appcontrol.SetConfigMode(); CSharedMemChannelMgnt mgntServer; // Create an endpoint. - EXPECT_NO_THROW(mgntServer.Initialize("")); + EXPECT_NO_THROW(mgntServer.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgntServer.GetObjectState(), sdv::EObjectState::initialized); mgntServer.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(mgntServer.GetObjectState(), sdv::EObjectState::running); - sdv::ipc::SChannelEndpoint sChannelEndpoint = mgntServer.CreateEndpoint(R"code( + sdv::ipc::SChannelEndpoint sChannelEndpoint = mgntServer.CreateEndpoint(R"toml( [IpcChannel] Size = 1024000 -)code"); +)toml"); EXPECT_NE(sChannelEndpoint.pConnection, nullptr); EXPECT_FALSE(sChannelEndpoint.ssConnectString.empty()); diff --git a/tests/unit_tests/simple_toml_parser/CMakeLists.txt b/tests/unit_tests/simple_toml_parser/CMakeLists.txt new file mode 100644 index 0000000..3fc9805 --- /dev/null +++ b/tests/unit_tests/simple_toml_parser/CMakeLists.txt @@ -0,0 +1,46 @@ +#******************************************************************************* +# Copyright (c) 2025-2026 ZF Friedrichshafen AG +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# +# Contributors: +# Martin Stimpfl - initial API and implementation +# Erik Verhoeven - writing TOML and whitespace preservation +#******************************************************************************* + +# Define project +project (UnitTest_SimpleTOMLParser VERSION 1.0 LANGUAGES CXX) + +# Character Reader executable +add_executable(UnitTest_SimpleTOMLParser + "parser_tests.cpp" "main.cpp") + +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + target_link_libraries(UnitTest_SimpleTOMLParser GTest::GTest ${CMAKE_THREAD_LIBS_INIT}) + if (WIN32) + target_link_libraries(UnitTest_SimpleTOMLParser Ws2_32 Winmm Rpcrt4.lib) + else() + target_link_libraries(UnitTest_SimpleTOMLParser ${CMAKE_DL_LIBS} rt) + endif() +else() + target_link_libraries(UnitTest_SimpleTOMLParser GTest::GTest Rpcrt4.lib) +endif() + +# Add the Reader unittest +add_test(NAME UnitTest_SimpleTOMLParser COMMAND UnitTest_SimpleTOMLParser) + +# Execute the test +add_custom_command(TARGET UnitTest_SimpleTOMLParser POST_BUILD + COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake "$" --gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_SimpleTOMLParser.xml + VERBATIM +) + +# Build dependencies +#add_dependencies(UnitTest_SimpleTOMLParser dependency_sdv_components) + + + diff --git a/tests/unit_tests/simple_toml_parser/main.cpp b/tests/unit_tests/simple_toml_parser/main.cpp new file mode 100644 index 0000000..ef47e4c --- /dev/null +++ b/tests/unit_tests/simple_toml_parser/main.cpp @@ -0,0 +1,28 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Martin Stimpfl - initial API and implementation + * Erik Verhoeven - writing TOML and whitespace preservation + ********************************************************************************/ + +#include +#include "../../../global/process_watchdog.h" + +#if defined(_WIN32) && defined(_UNICODE) +extern "C" int wmain(int argc, wchar_t* argv[]) +#else +extern "C" int main(int argc, char* argv[]) +#endif +{ + CProcessWatchdog watchdog; + + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/unit_tests/simple_toml_parser/parser_tests.cpp b/tests/unit_tests/simple_toml_parser/parser_tests.cpp new file mode 100644 index 0000000..64b197a --- /dev/null +++ b/tests/unit_tests/simple_toml_parser/parser_tests.cpp @@ -0,0 +1,868 @@ +#include +#include +#include + +TEST(RecognizeTypes, Root) +{ + sdv::toml::simple_parser::CParser parser(""); + auto sRoot = parser.Root(); + EXPECT_EQ(sRoot.GetType(), sdv::toml::simple_parser::ENodeType::node_table); +} + +TEST(RecognizeTypes, Table) +{ + sdv::toml::simple_parser::CParser parser(R"toml( + [newTable] + [secondTable.nestedTable] + )toml"); + auto sRoot = parser.Root(); + + auto table1 = sRoot.GetDirect("newTable"); + EXPECT_EQ(table1.GetType(), sdv::toml::simple_parser::ENodeType::node_table); + EXPECT_EQ(table1.GetName(), "newTable"); + EXPECT_EQ(table1.GetValue(), ""); + + auto table2 = sRoot.GetDirect("secondTable"); + EXPECT_EQ(table2.GetType(), sdv::toml::simple_parser::ENodeType::node_table); + EXPECT_EQ(table2.GetName(), "secondTable"); + EXPECT_EQ(table2.GetValue(), ""); + + auto table3 = sRoot.GetDirect("secondTable.nestedTable"); + EXPECT_EQ(table3.GetType(), sdv::toml::simple_parser::ENodeType::node_table); + EXPECT_EQ(table3.GetName(), "nestedTable"); + EXPECT_EQ(table3.GetValue(), ""); +} + +TEST(RecognizeTypes, Key_Value) +{ + sdv::toml::simple_parser::CParser parser(R"toml( + name = "Hammer" + id = 42 + pi = 3.1415926 + boolean = true + array = [] + table = {} + )toml"); + auto sRoot = parser.Root(); + + auto value_name = sRoot.GetDirect("name"); + EXPECT_EQ(value_name.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(value_name.GetValue(), "Hammer"); + + auto value_id = sRoot.GetDirect("id"); + EXPECT_EQ(value_id.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(value_id.GetValue(), 42); + + auto value_pi = sRoot.GetDirect("pi"); + EXPECT_EQ(value_pi.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(value_pi.GetValue(), 3.1415926); + + auto value_boolean = sRoot.GetDirect("boolean"); + EXPECT_EQ(value_boolean.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(value_boolean.GetValue(), true); + + auto value_array = sRoot.GetDirect("array"); + EXPECT_EQ(value_array.GetType(), sdv::toml::simple_parser::ENodeType::node_array); + EXPECT_EQ(value_array.GetValue(), ""); + + auto value_table = sRoot.GetDirect("table"); + EXPECT_EQ(value_table.GetType(), sdv::toml::simple_parser::ENodeType::node_table); + EXPECT_EQ(value_table.GetValue(), ""); +} + +TEST(RecognizeTypes, TableArray) +{ + sdv::toml::simple_parser::CParser parser(R"toml( + [[newTableArray]] + [[newTableArray]] + [[table.nestedTableArray]] + )toml"); + auto sRoot = parser.Root(); + + auto tableArray1 = sRoot.GetDirect("newTableArray"); + EXPECT_EQ(tableArray1.GetType(), sdv::toml::simple_parser::ENodeType::node_array); + EXPECT_EQ(tableArray1.GetName(), "newTableArray"); + + auto table1 = sRoot.GetDirect("newTableArray[0]"); + ASSERT_TRUE(table1); + EXPECT_EQ(table1.GetType(), sdv::toml::simple_parser::ENodeType::node_table); + EXPECT_EQ(table1.GetName(), "newTableArray"); + + auto table2 = sRoot.GetDirect("newTableArray[1]"); + ASSERT_TRUE(table2); + EXPECT_EQ(table2.GetType(), sdv::toml::simple_parser::ENodeType::node_table); + EXPECT_EQ(table2.GetName(), "newTableArray"); +} + +TEST(NestedContent, Array) +{ + sdv::toml::simple_parser::CParser parser(R"toml( + arr_mixed = [ 1.0, 2, "test string", [ 1, 2 ], { pi = 3.14, e = 2.71828 }, true] + arr_ints = [ 1, 2, 3, 4] + arr_ints_trailing_comma = [ 1, 2, 3, 4, ] + arr_multiline = [ + "first line", + "second line", + "third_line", + ] + )toml"); + auto sRoot = parser.Root(); + + { + auto array_ints = sRoot.GetDirect("arr_ints"); + EXPECT_EQ(array_ints.GetType(), sdv::toml::simple_parser::ENodeType::node_array); + auto array_ints_0 = sRoot.GetDirect("arr_ints[0]"); + ASSERT_TRUE(array_ints_0); + EXPECT_EQ(array_ints_0.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(array_ints_0.GetValue(), 1); + auto array_ints_1 = sRoot.GetDirect("arr_ints[1]"); + ASSERT_TRUE(array_ints_1); + EXPECT_EQ(array_ints_1.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(array_ints_1.GetValue(), 2); + auto array_ints_2 = sRoot.GetDirect("arr_ints[2]"); + ASSERT_TRUE(array_ints_2); + EXPECT_EQ(array_ints_2.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(array_ints_2.GetValue(), 3); + auto array_ints_3 = sRoot.GetDirect("arr_ints[3]"); + ASSERT_TRUE(array_ints_3); + EXPECT_EQ(array_ints_3.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(array_ints_3.GetValue(), 4); + auto array_ints_4 = sRoot.GetDirect("arr_ints[4]"); + EXPECT_FALSE(array_ints_4); + } + + { + auto array_ints_trailing_comma = sRoot.GetDirect("arr_ints_trailing_comma"); + auto array_ints_trailing_comma_0 = sRoot.GetDirect("arr_ints_trailing_comma[0]"); + auto array_ints_trailing_comma_1 = sRoot.GetDirect("arr_ints_trailing_comma[1]"); + auto array_ints_trailing_comma_2 = sRoot.GetDirect("arr_ints_trailing_comma[2]"); + auto array_ints_trailing_comma_3 = sRoot.GetDirect("arr_ints_trailing_comma[3]"); + auto array_ints_trailing_comma_4 = sRoot.GetDirect("arr_ints_trailing_comma[4]"); + + EXPECT_EQ(array_ints_trailing_comma.GetType(), sdv::toml::simple_parser::ENodeType::node_array); + ASSERT_TRUE(array_ints_trailing_comma_0); + EXPECT_EQ(array_ints_trailing_comma_0.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(array_ints_trailing_comma_0.GetValue(), 1); + ASSERT_TRUE(array_ints_trailing_comma_1); + EXPECT_EQ(array_ints_trailing_comma_1.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(array_ints_trailing_comma_1.GetValue(), 2); + ASSERT_TRUE(array_ints_trailing_comma_2); + EXPECT_EQ(array_ints_trailing_comma_2.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(array_ints_trailing_comma_2.GetValue(), 3); + ASSERT_TRUE(array_ints_trailing_comma_3); + EXPECT_EQ(array_ints_trailing_comma_3.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(array_ints_trailing_comma_3.GetValue(), 4); + EXPECT_FALSE(array_ints_trailing_comma_4); + } + + { + auto array_mixed = sRoot.GetDirect("arr_mixed"); + auto array_mixed_0 = sRoot.GetDirect("arr_mixed[0]"); + auto array_mixed_1 = sRoot.GetDirect("arr_mixed[1]"); + auto array_mixed_2 = sRoot.GetDirect("arr_mixed[2]"); + auto array_mixed_3 = sRoot.GetDirect("arr_mixed[3]"); + auto array_mixed_3_1 = sRoot.GetDirect("arr_mixed[3][0]"); + auto array_mixed_3_2 = sRoot.GetDirect("arr_mixed[3][1]"); + auto array_mixed_4 = sRoot.GetDirect("arr_mixed[4]"); + auto array_mixed_4_pi = sRoot.GetDirect("arr_mixed[4].pi"); + auto array_mixed_4_e = sRoot.GetDirect("arr_mixed[4].e"); + auto array_mixed_5 = sRoot.GetDirect("arr_mixed[5]"); + auto array_mixed_6 = sRoot.GetDirect("arr_mixed[6]"); + + EXPECT_EQ(array_mixed.GetType(), sdv::toml::simple_parser::ENodeType::node_array); + ASSERT_TRUE(array_mixed_0); + EXPECT_EQ(array_mixed_0.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(array_mixed_0.GetValue(), 1.0); + ASSERT_TRUE(array_mixed_1); + EXPECT_EQ(array_mixed_1.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(array_mixed_1.GetValue(), 2); + ASSERT_TRUE(array_mixed_2); + EXPECT_EQ(array_mixed_2.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(array_mixed_2.GetValue(), "test string"); + ASSERT_TRUE(array_mixed_3); + EXPECT_EQ(array_mixed_3.GetType(), sdv::toml::simple_parser::ENodeType::node_array); + EXPECT_EQ(array_mixed_3_1.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(array_mixed_3_1.GetValue(), 1); + EXPECT_EQ(array_mixed_3_2.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(array_mixed_3_2.GetValue(), 2); + ASSERT_TRUE(array_mixed_4); + EXPECT_EQ(array_mixed_4.GetType(), sdv::toml::simple_parser::ENodeType::node_table); + EXPECT_EQ(array_mixed_4_pi.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(array_mixed_4_pi.GetValue(), 3.14); + EXPECT_EQ(array_mixed_4_e.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(array_mixed_4_e.GetValue(), 2.71828); + ASSERT_TRUE(array_mixed_5); + EXPECT_EQ(array_mixed_5.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(array_mixed_5.GetValue(), true); + EXPECT_FALSE(array_mixed_6); + } + + { + auto array_multiline = sRoot.GetDirect("arr_multiline"); + auto array_multiline_0 = sRoot.GetDirect("arr_multiline[0]"); + auto array_multiline_1 = sRoot.GetDirect("arr_multiline[1]"); + auto array_multiline_2 = sRoot.GetDirect("arr_multiline[2]"); + auto array_multiline_3 = sRoot.GetDirect("arr_multiline[3]"); + + EXPECT_EQ(array_multiline.GetType(), sdv::toml::simple_parser::ENodeType::node_array); + ASSERT_TRUE(array_multiline_0); + EXPECT_EQ(array_multiline_0.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(array_multiline_0.GetValue(), "first line"); + ASSERT_TRUE(array_multiline_1); + EXPECT_EQ(array_multiline_1.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(array_multiline_1.GetValue(), "second line"); + ASSERT_TRUE(array_multiline_2); + EXPECT_EQ(array_multiline_2.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(array_multiline_2.GetValue(), "third_line"); + EXPECT_FALSE(array_multiline_3); + } +} + +TEST(NestedContent, Table) +{ + sdv::toml::simple_parser::CParser parser(R"toml( + [table] + a = 2 + b = 1.2 + [anotherTable] + a = 4 + c = false + [thirdTable.fourthTable] + a = "five" + d = [] + )toml"); + auto sRoot = parser.Root(); + + auto table_a = sRoot.GetDirect("table.a"); + auto table_b = sRoot.GetDirect("table.b"); + auto anotherTable_a = sRoot.GetDirect("anotherTable.a"); + auto anotherTable_c = sRoot.GetDirect("anotherTable.c"); + auto fourthTable_a = sRoot.GetDirect("thirdTable.fourthTable.a"); + auto fourthTable_d = sRoot.GetDirect("thirdTable.fourthTable.d"); + + ASSERT_TRUE(table_a); + EXPECT_EQ(table_a.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(table_a.GetValue(), 2); + ASSERT_TRUE(table_b); + EXPECT_EQ(table_b.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(table_b.GetValue(), 1.2); + ASSERT_TRUE(anotherTable_a); + EXPECT_EQ(anotherTable_a.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(anotherTable_a.GetValue(), 4); + ASSERT_TRUE(anotherTable_c); + EXPECT_EQ(anotherTable_c.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(anotherTable_c.GetValue(), false); + ASSERT_TRUE(fourthTable_a); + EXPECT_EQ(fourthTable_a.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(fourthTable_a.GetValue(), "five"); + ASSERT_TRUE(fourthTable_d); + EXPECT_EQ(fourthTable_d.GetType(), sdv::toml::simple_parser::ENodeType::node_array); +} + +TEST(NestedContent, TableArray) +{ + sdv::toml::simple_parser::CParser parser(R"toml( + [[table.test]] + a = 2 + b = 1.2 + [[table.test]] + a = 4 + c = false + [[table.test]] + a = "five" + d = [] + )toml"); + auto sRoot = parser.Root(); + + auto table_test_1_a = sRoot.GetDirect("table.test[0].a"); + auto table_test_1_b = sRoot.GetDirect("table.test[0].b"); + auto table_test_2_a = sRoot.GetDirect("table.test[1].a"); + auto table_test_2_c = sRoot.GetDirect("table.test[1].c"); + auto table_test_3_a = sRoot.GetDirect("table.test[2].a"); + auto table_test_3_d = sRoot.GetDirect("table.test[2].d"); + + ASSERT_TRUE(table_test_1_a); + EXPECT_EQ(table_test_1_a.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(table_test_1_a.GetValue(), 2); + ASSERT_TRUE(table_test_1_b); + EXPECT_EQ(table_test_1_b.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(table_test_1_b.GetValue(), 1.2); + ASSERT_TRUE(table_test_2_a); + EXPECT_EQ(table_test_2_a.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(table_test_2_a.GetValue(), 4); + ASSERT_TRUE(table_test_2_c); + EXPECT_EQ(table_test_2_c.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(table_test_2_c.GetValue(), false); + ASSERT_TRUE(table_test_3_a); + EXPECT_EQ(table_test_3_a.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(table_test_3_a.GetValue(), "five"); + ASSERT_TRUE(table_test_3_d); + EXPECT_EQ(table_test_3_d.GetType(), sdv::toml::simple_parser::ENodeType::node_array); +} + +TEST(NestedContent, InlineTable) +{ + sdv::toml::simple_parser::CParser parser(R"toml( + table1 = { a = 0, b = 1.2, c = "string" } + table2 = { a = [], b = true, e = 2.71828 } + table3 = { a = { a = "a", b = "A" }, b = {a = "b", b = "B"}, e = {a = "e", b = "E"} } + )toml"); + auto sRoot = parser.Root(); + + auto table1_a = sRoot.GetDirect("table1.a"); + auto table1_b = sRoot.GetDirect("table1.b"); + auto table1_c = sRoot.GetDirect("table1.c"); + auto table2_a = sRoot.GetDirect("table2.a"); + auto table2_b = sRoot.GetDirect("table2.b"); + auto table2_e = sRoot.GetDirect("table2.e"); + auto table3_a_a = sRoot.GetDirect("table3.a.a"); + auto table3_a_b = sRoot.GetDirect("table3.a.b"); + auto table3_b_a = sRoot.GetDirect("table3.b.a"); + auto table3_b_b = sRoot.GetDirect("table3.b.b"); + auto table3_e_a = sRoot.GetDirect("table3.e.a"); + auto table3_e_b = sRoot.GetDirect("table3.e.b"); + + ASSERT_TRUE(table1_a); + EXPECT_EQ(table1_a.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(table1_a.GetValue(), 0); + ASSERT_TRUE(table1_b); + EXPECT_EQ(table1_b.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(table1_b.GetValue(), 1.2); + ASSERT_TRUE(table1_c); + EXPECT_EQ(table1_c.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(table1_c.GetValue(), "string"); + ASSERT_TRUE(table2_a); + EXPECT_EQ(table2_a.GetType(), sdv::toml::simple_parser::ENodeType::node_array); + ASSERT_TRUE(table2_b); + EXPECT_EQ(table2_b.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(table2_b.GetValue(), true); + ASSERT_TRUE(table2_e); + EXPECT_EQ(table2_e.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(table2_e.GetValue(), 2.71828); + ASSERT_TRUE(table3_a_a); + EXPECT_EQ(table3_a_a.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(table3_a_a.GetValue(), "a"); + ASSERT_TRUE(table3_a_b); + EXPECT_EQ(table3_a_b.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(table3_a_b.GetValue(), "A"); + ASSERT_TRUE(table3_b_a); + EXPECT_EQ(table3_b_a.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(table3_b_a.GetValue(), "b"); + ASSERT_TRUE(table3_b_b); + EXPECT_EQ(table3_b_b.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(table3_b_b.GetValue(), "B"); + ASSERT_TRUE(table3_e_a); + EXPECT_EQ(table3_e_a.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(table3_e_a.GetValue(), "e"); + ASSERT_TRUE(table3_e_b); + EXPECT_EQ(table3_e_b.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(table3_e_b.GetValue(), "E"); +} + +TEST(NestedContent, InlineTableBreakLine) +{ + // The following is not allowed in version 1.0, but is allowed in version 1.1 + EXPECT_NO_THROW(sdv::toml::simple_parser::CParser(R"toml( +table = { a = 1, b = 2, + c = 3, d = 4 } +)toml")); + EXPECT_NO_THROW(sdv::toml::simple_parser::CParser(R"toml( +table = { a = 1, b = 2 + ,c = 3, d = 4 } +)toml")); + + // Line breaks are allowed when part of an array or have multi-line strings + EXPECT_NO_THROW(sdv::toml::simple_parser::CParser(R"toml( +array = [{ a = 1, b = 2}, + {c = 3, d = 4}] +)toml")); + EXPECT_NO_THROW(sdv::toml::simple_parser::CParser(R"toml( +table = { a = 1, b = [2, 3, + 4, 5], c = 6, d = 7} +)toml")); + EXPECT_NO_THROW(sdv::toml::simple_parser::CParser(R"toml( +table = { x = "abc", y = """def- +ghi""", z = "jkl" } +)toml")); + EXPECT_NO_THROW(sdv::toml::simple_parser::CParser(R"toml( +table = { x = 'abc', y = '''def- +ghi''', z = 'jkl' } +)toml")); +} + +TEST(SpecialCases, Keys) +{ + std::string ssUTF8String = u8R"toml( + "127.0.0.1" = "value" + "character encoding" = "value" + "ʎǝʞ" = "value" + 'key2' = "value" + 'quoted "value"' = "value" + )toml"; + EXPECT_NO_THROW(sdv::toml::simple_parser::CParser(std::string_view(ssUTF8String))); + + EXPECT_NO_THROW(sdv::toml::simple_parser::CParser(R"toml( + key = "value" + bare_key = "value" + bare-key = "value" + 1234 = "value" + )toml")); + + EXPECT_NO_THROW(sdv::toml::simple_parser::CParser(R"toml( + "" = "blank" # VALID but discouraged + )toml")); + EXPECT_NO_THROW(sdv::toml::simple_parser::CParser(R"toml( + '' = 'blank' # VALID but discouraged + )toml")); + + EXPECT_NO_THROW(sdv::toml::simple_parser::CParser(R"toml( + name = "Orange" + physical.color = "orange" + physical.shape = "round" + site."google.com" = true + )toml")); + + + EXPECT_NO_THROW(sdv::toml::simple_parser::CParser(R"toml( + fruit.name = "banana" # this is best practice + fruit. color = "yellow" # same as fruit.color + fruit . flavor = "banana" # same as fruit.flavor + )toml")); + + EXPECT_NO_THROW(sdv::toml::simple_parser::CParser(R"toml( + # This makes the key "fruit" into a table. + fruit.apple.smooth = true + # So then you can add to the table "fruit" like so: + fruit.orange = 2 + )toml")); + + EXPECT_NO_THROW(sdv::toml::simple_parser::CParser(R"toml( + # VALID BUT DISCOURAGED + apple.type = "fruit" + orange.type = "fruit" + apple.skin = "thin" + orange.skin = "thick" + apple.color = "red" + orange.color = "orange" + )toml")); + + EXPECT_NO_THROW(sdv::toml::simple_parser::CParser(R"toml( + 3.1415 = 3.1415 + )toml")); + { + sdv::toml::simple_parser::CParser parser(R"toml( + 3.1415 = 3.1415 + )toml"); + auto sRoot = parser.Root(); + + auto table = sRoot.GetDirect("3"); + auto pi = sRoot.GetDirect("3.1415"); + ASSERT_TRUE(table); + EXPECT_EQ(table.GetType(), sdv::toml::simple_parser::ENodeType::node_table); + ASSERT_TRUE(pi); + EXPECT_EQ(pi.GetType(), sdv::toml::simple_parser::ENodeType::node_value); + EXPECT_EQ(pi.GetValue(), 3.1415); + } +} + +TEST(SpecialCases, Arrays) +{ + EXPECT_NO_THROW(sdv::toml::simple_parser::CParser(R"toml( + integers = [ 1, 2, 3 ] + colors = [ "red", "yellow", "green" ] + nested_arrays_of_ints = [ [ 1, 2 ], [3, 4, 5] ] + nested_mixed_array = [ [ 1, 2 ], ["a", "b", "c"] ] + string_array = [ "all", 'strings', """are the same""", '''type''' ] + )toml")); + + EXPECT_NO_THROW(sdv::toml::simple_parser::CParser(R"toml( + numbers = [ 0.1, 0.2, 0.5, 1, 2, 5 ] + contributors = [ + "Foo Bar ", + { name = "Baz Qux", email = "bazqux@example.com", url = "https://example.com/bazqux" } + ] + )toml")); + + EXPECT_NO_THROW(sdv::toml::simple_parser::CParser(R"toml( + integers3 = [ + 1, + 2, # this is ok + ] + )toml")); +} + +TEST(SpecialCases, Tables) +{ + EXPECT_NO_THROW(sdv::toml::simple_parser::CParser(R"toml( + [table-1] + key1 = "some string" + key2 = 123 + + [table-2] + key1 = "another string" + key2 = 456 + )toml")); + + EXPECT_NO_THROW(sdv::toml::simple_parser::CParser(R"toml( + [dog."tater.man"] + type.name = "pug" + )toml")); + + std::string ssUTF8String = u8R"toml( + [a.b.c] # this is best practice + [ d.e.f ] # same as [d.e.f] + [ g . h . i ] # same as [g.h.i] + [ j . "ʞ" . 'l' ] # same as [j."ʞ".'l'] + )toml"; + EXPECT_NO_THROW(sdv::toml::simple_parser::CParser(std::string_view(ssUTF8String))); + + EXPECT_NO_THROW(sdv::toml::simple_parser::CParser(R"toml( + # [x] you + # [x.y] don't + # [x.y.z] need these + [x.y.z.w] # for this to work + [x] # defining a super-table afterward is ok + )toml")); + + EXPECT_NO_THROW(sdv::toml::simple_parser::CParser(R"toml( + # VALID BUT DISCOURAGED + [fruit.apple] + [animal] + [fruit.orange] + )toml")); + + EXPECT_NO_THROW(sdv::toml::simple_parser::CParser(R"toml( + [fruit] + apple.color = "red" + apple.taste.sweet = true + [fruit.apple.texture] # you can add sub-tables + )toml")); +} + +TEST(SpecialCases, TableArrays) +{ + EXPECT_NO_THROW(sdv::toml::simple_parser::CParser(R"toml( + [[products]] + name = "Hammer" + sku = 738594937 + [[products]] # empty table within the array + [[products]] + name = "Nail" + sku = 284758393 + color = "gray" + )toml")); + + EXPECT_NO_THROW(sdv::toml::simple_parser::CParser(R"toml( + [[fruits]] + name = "apple" + [fruits.physical] # subtable + color = "red" + shape = "round" + [[fruits.varieties]] # nested array of tables + name = "red delicious" + [[fruits.varieties]] + name = "granny smith" + [[fruits]] + name = "banana" + [[fruits.varieties]] + name = "plantain" + )toml")); + + EXPECT_NO_THROW(sdv::toml::simple_parser::CParser(R"toml( + points = [ { x = 1, y = 2, z = 3 }, + { x = 7, y = 8, z = 9 }, + { x = 2, y = 4, z = 8 } ] + )toml")); +} + +TEST(ErrorCases, KeyValue) +{ + EXPECT_THROW(sdv::toml::simple_parser::CParser(R"toml(key = # node_invalid)toml"), std::exception); + + EXPECT_THROW( + sdv::toml::simple_parser::CParser(R"toml(first = "Tom" last = "Preston-Werner" # node_invalid)toml"), std::exception); + + EXPECT_THROW(sdv::toml::simple_parser::CParser(R"toml(= "no key name" # node_invalid)toml"), std::exception); + + EXPECT_THROW(sdv::toml::simple_parser::CParser(R"toml( + name = "Tom" + name = "Pradyun" + )toml"), + std::exception); + + EXPECT_THROW(sdv::toml::simple_parser::CParser(R"toml( + fruit . flavor = "banana" # same as fruit.flavor + fruit.flavor = "banana" + )toml"), + std::exception); + + EXPECT_THROW(sdv::toml::simple_parser::CParser(R"toml( + spelling = "favorite" + "spelling" = "favourite" + )toml"), + std::exception); + + + EXPECT_THROW(sdv::toml::simple_parser::CParser(R"toml( + # This defines the value of fruit.apple to be an integer. + fruit.apple = 1 + # But then this treats fruit.apple like it's a table. + # You can't turn an integer into a table. + fruit.apple.smooth = true + )toml"), + std::exception); +} + +TEST(ErrorCases, Tables) +{ + std::string ssUTF8String(u8R"toml( + [ j . "ʞ" . 'l' ] + [j."ʞ".'l'] + )toml"); + EXPECT_THROW(sdv::toml::simple_parser::CParser(std::string_view(ssUTF8String)), std::exception); + + ssUTF8String = u8R"toml( + [ j . "ʞ" . 'l' ] + ["j".'ʞ'."l"] + )toml"; + EXPECT_THROW(sdv::toml::simple_parser::CParser(std::string_view(ssUTF8String)), std::exception); + + EXPECT_THROW(sdv::toml::simple_parser::CParser(R"toml( + [fruit] + apple = "red" + [fruit] + orange = "orange" + )toml"), + std::exception); + + EXPECT_THROW(sdv::toml::simple_parser::CParser(R"toml( + [fruit] + apple = "red" + [fruit.apple] + texture = "smooth" + )toml"), + std::exception); + + // These two tests are not covered with current implementation. + //EXPECT_THROW(sdv::toml::simple_parser::CParser(R"toml( + // [fruit] + // apple.color = "red" + // apple.taste.sweet = true + // [fruit.apple] # INVALID + // )toml"), + // std::exception); + //EXPECT_THROW(sdv::toml::simple_parser::CParser(R"toml( + // [fruit] + // apple.color = "red" + // apple.taste.sweet = true + // [fruit.apple.taste] # INVALID + // )toml"), + // std::exception); +} + +TEST(ErrorCases, InlineTables) +{ + EXPECT_THROW(sdv::toml::simple_parser::CParser(R"toml( + type = { name = "Nail" } + type.edible = false # INVALID + )toml"), + std::exception); + EXPECT_THROW(sdv::toml::simple_parser::CParser(R"toml( + [product] + type.name = "Nail" + type = { edible = false } # INVALID + )toml"), + std::exception); +} + +TEST(ErrorCases, TableArrays) +{ + EXPECT_THROW(sdv::toml::simple_parser::CParser(R"toml( + [fruit.physical] # subtable, but to which parent element should it belong? + color = "red" + shape = "round" + [[fruit]] # parser must throw an error upon discovering that "fruit" is + # an array rather than a table + name = "apple" + )toml"), + std::exception); + + // The following test is not covered with the current implementation + //EXPECT_THROW(sdv::toml::simple_parser::CParser(R"toml( + // fruits = [] + // [[fruits]] # Not allowed + // )toml"), + // std::exception); + + EXPECT_THROW(sdv::toml::simple_parser::CParser(R"toml( + [[fruits]] + name = "apple" + [[fruits.varieties]] + name = "red delicious" + # INVALID: This table conflicts with the previous array of tables + [fruits.varieties] + name = "granny smith" + )toml"), + std::exception); + EXPECT_THROW(sdv::toml::simple_parser::CParser(R"toml( + [[fruits]] + name = "apple" + [fruits.physical] + color = "red" + shape = "round" + # INVALID: This array of tables conflicts with the previous table + [[fruits.physical]] + color = "green" + )toml"), + std::exception); +} + +TEST(Ordering, Array) +{ + sdv::toml::simple_parser::CParser parser(R"toml( + array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] + )toml"); + auto sRoot = parser.Root(); + + auto two = sRoot.GetDirect("array[2]"); + auto eleven = sRoot.GetDirect("array[11]"); + const auto arr = sRoot.GetDirect("array"); + + // with direct access + ASSERT_TRUE(two); + EXPECT_EQ(two.GetValue(), 2); + ASSERT_TRUE(eleven); + EXPECT_EQ(eleven.GetValue(), 11); + + // with indirect access through iterating + ASSERT_TRUE(arr); + EXPECT_EQ(arr.GetArray().size(), 12u); + size_t nIndex = 0; + for (const auto& rsNode : arr.GetArray()) + EXPECT_EQ(rsNode.GetValue(), nIndex++); +} + +TEST(Ordering, TableAray) +{ + sdv::toml::simple_parser::CParser parser(R"toml( + [[tableArray]] + a = 0 + [[tableArray]] + a = 1 + [[tableArray]] + a = 2 + [[tableArray]] + a = 3 + [[tableArray]] + a = 4 + [[tableArray]] + a = 5 + [[tableArray]] + a = 6 + [[tableArray]] + a = 7 + [[tableArray]] + a = 8 + [[tableArray]] + a = 9 + [[tableArray]] + a = 10 + [[tableArray]] + a = 11 + )toml"); + auto sRoot = parser.Root(); + + auto tableArray = sRoot.GetDirect("tableArray"); + + ASSERT_TRUE(tableArray); + EXPECT_EQ(tableArray.GetArray().size(), 12u); + size_t nIndex = 0; + for (const auto& rsNode : tableArray.GetArray()) + EXPECT_EQ(rsNode.GetDirect("a").GetValue(), nIndex++); +} + +TEST(Ordering, TableArayWithTables) +{ + sdv::toml::simple_parser::CParser parser(R"toml( + [topTable] + [[topTable.tableArray]] + [topTable.tableArray.MyTable] + a = 0 + [[topTable.tableArray]] + [topTable.tableArray.MyTable] + a = 1 + [[topTable.tableArray]] + [topTable.tableArray.MyTable] + a = 2 + [[topTable.tableArray]] + [topTable.tableArray.MyTable] + a = 3 + [[topTable.tableArray]] + [topTable.tableArray.MyTable] + a = 4 + [[topTable.tableArray]] + [topTable.tableArray.MyTable] + a = 5 + [[topTable.tableArray]] + [topTable.tableArray.MyTable] + a = 6 + [[topTable.tableArray]] + [topTable.tableArray.MyTable] + a = 7 + [[topTable.tableArray]] + [topTable.tableArray.MyTable] + a = 8 + [[topTable.tableArray]] + [topTable.tableArray.MyTable] + a = 9 + [[topTable.tableArray]] + [topTable.tableArray.MyTable] + a = 10 + [[topTable.tableArray]] + [topTable.tableArray.MyTable] + a = 11 + )toml"); + auto sRoot = parser.Root(); + + auto tableArray = sRoot.GetDirect("topTable.tableArray"); + + ASSERT_TRUE(tableArray); + EXPECT_EQ(tableArray.GetArray().size(), 12u); + size_t nIndex = 0; + for (const auto& rsNode : tableArray.GetArray()) + EXPECT_EQ(rsNode.GetDirect("MyTable.a").GetValue(), nIndex++); +} + +TEST(Ordering, NodeGetDirect) +{ + sdv::toml::simple_parser::CParser parser(R"toml( + [[table.test]] + a = 2 + b = 1.2 + [[table.test]] + a = 4 + c = false + [[table.test]] + a = "five" + d = [ { x = 1, y = 2, z = 3 }, + { x = 7, y = 8, z = 9 }, + { x = 2, y = 4, z = 8 }] + )toml"); + auto sRoot = parser.Root(); + + auto table_test_1_a = sRoot.GetDirect("table.test[0].a"); + auto table_test_1_b = sRoot.GetDirect("table.test[0].b"); + auto table_test_2_a = sRoot.GetDirect("table.test[1].a"); + auto table_test_2_c = sRoot.GetDirect("table.test[1].c"); + auto table_test_3_a = sRoot.GetDirect("table.test[2].a"); + auto table_test_3_d = sRoot.GetDirect("table.test[2].d"); + + EXPECT_TRUE(table_test_1_a); + EXPECT_TRUE(table_test_1_b); + EXPECT_EQ(table_test_3_d.GetType(), sdv::toml::simple_parser::ENodeType::node_array); + + auto table_test_3 = sRoot.GetDirect("table.test[2]"); + auto table_test_3_2nd = table_test_3.GetDirect("d[2].x"); + ASSERT_TRUE(table_test_3_2nd); +} + diff --git a/tests/unit_tests/socket_can_com_tests/src/can_com_test_socket.cpp b/tests/unit_tests/socket_can_com_tests/src/can_com_test_socket.cpp index 66dde82..38e9287 100644 --- a/tests/unit_tests/socket_can_com_tests/src/can_com_test_socket.cpp +++ b/tests/unit_tests/socket_can_com_tests/src/can_com_test_socket.cpp @@ -96,15 +96,15 @@ bool CANSocketTest::vcanIsInstalled = false; class CTestCANSocket : public CCANSockets { public: - virtual void Initialize(const sdv::u8string& ssObjectConfig) override - { - CCANSockets::Initialize(ssObjectConfig); - } + // virtual void OnInitialize(const sdv::ObjectInfo& sObjectInfo) override + // { + // CCANSockets::OnInitialize(sObjectInfo); + // } - virtual void Shutdown() override - { - CCANSockets::Shutdown(); - } + // virtual void Shutdown() override + // { + // CCANSockets::Shutdown(); + // } virtual void Send(const sdv::can::SMessage& sMsg, uint32_t uiIfcIndex) override { @@ -199,7 +199,9 @@ bool InitializeAppControl(sdv::app::CAppControl* appcontrol, const std::string& void InitializeCanComObject(CTestCANSocket& canComObj, const std::string config, MockCANReceiver& mockRcv) { - ASSERT_NO_THROW(canComObj.Initialize(config.c_str())); + sdv::SObjectInfo sObjectInfo{}; + sObjectInfo.ssConfig = config; + ASSERT_NO_THROW(canComObj.Initialize(sObjectInfo)); EXPECT_EQ(canComObj.GetObjectState(), sdv::EObjectState::initialized); ASSERT_NO_THROW(canComObj.SetOperationMode(sdv::EOperationMode::configuring)); ASSERT_NO_THROW(canComObj.RegisterReceiver(&mockRcv)); @@ -228,9 +230,10 @@ TEST_F(CANSocketTest, ValidConfigString) sdv::app::CAppControl appControl; appControl.Startup(""); - sdv::u8string ssObjectConfig = R"(canSockets = "vcan0")"; // vcan0 interface must exist + sdv::SObjectInfo sObjectInfo{}; + sObjectInfo.ssConfig = R"(canSockets = "vcan0")"; // vcan0 interface must exist CTestCANSocket canComObj; - ASSERT_NO_THROW(canComObj.Initialize(ssObjectConfig.c_str())); + ASSERT_NO_THROW(canComObj.Initialize(sObjectInfo)); ASSERT_EQ(canComObj.GetObjectState(), sdv::EObjectState::initialized); ASSERT_NO_THROW(canComObj.Send(testMsg, 0)); @@ -244,9 +247,10 @@ TEST_F(CANSocketTest, InvalidConfigString) sdv::app::CAppControl appControl; appControl.Startup(""); - sdv::u8string ssObjectConfig = R"(canSockets = "vcan08")"; + sdv::SObjectInfo sObjectInfo{}; + sObjectInfo.ssConfig = R"(canSockets = "vcan08")"; CTestCANSocket canComObj; - ASSERT_NO_THROW(canComObj.Initialize(ssObjectConfig.c_str())); + ASSERT_NO_THROW(canComObj.Initialize(sObjectInfo)); EXPECT_EQ(canComObj.GetObjectState(), sdv::EObjectState::initialization_failure); ASSERT_NO_THROW(canComObj.Shutdown()); @@ -260,9 +264,10 @@ TEST_F(CANSocketTest, ValidConfigArray) sdv::app::CAppControl appControl; appControl.Startup(""); - sdv::u8string ssObjectConfig = R"(canSockets = ["vcan0", "vcan1"])"; + sdv::SObjectInfo sObjectInfo{}; + sObjectInfo.ssConfig = R"(canSockets = ["vcan0", "vcan1"])"; CTestCANSocket canComObj; - ASSERT_NO_THROW(canComObj.Initialize(ssObjectConfig.c_str())); + ASSERT_NO_THROW(canComObj.Initialize(sObjectInfo)); EXPECT_EQ(canComObj.GetObjectState(), sdv::EObjectState::initialized); ASSERT_NO_THROW(canComObj.Send(testMsg, 0)); @@ -276,9 +281,10 @@ TEST_F(CANSocketTest, InvalidConfigArray) sdv::app::CAppControl appControl; appControl.Startup(""); - sdv::u8string ssObjectConfig = R"(canSockets = ["vcan08", "vcan09"])"; + sdv::SObjectInfo sObjectInfo{}; + sObjectInfo.ssConfig = R"(canSockets = ["vcan08", "vcan09"])"; CTestCANSocket canComObj; - ASSERT_NO_THROW(canComObj.Initialize(ssObjectConfig.c_str())); + ASSERT_NO_THROW(canComObj.Initialize(sObjectInfo)); EXPECT_EQ(canComObj.GetObjectState(), sdv::EObjectState::initialization_failure); ASSERT_NO_THROW(canComObj.Shutdown()); @@ -292,9 +298,10 @@ TEST_F(CANSocketTest, ValidConfigArrayButUnknownElement) sdv::app::CAppControl appControl; appControl.Startup(""); - sdv::u8string ssObjectConfig = R"(canSockets = ["vcan0", "vcan08"])"; + sdv::SObjectInfo sObjectInfo{}; + sObjectInfo.ssConfig = R"(canSockets = ["vcan0", "vcan08"])"; CTestCANSocket canComObj; - ASSERT_NO_THROW(canComObj.Initialize(ssObjectConfig.c_str())); + ASSERT_NO_THROW(canComObj.Initialize(sObjectInfo)); EXPECT_EQ(canComObj.GetObjectState(), sdv::EObjectState::initialized); ASSERT_NO_THROW(canComObj.Send(testMsg, 0)); @@ -308,9 +315,10 @@ TEST_F(CANSocketTest, InvalidConfigIdentifier) sdv::app::CAppControl appControl; appControl.Startup(""); - sdv::u8string ssObjectConfig = R"(invalidCanSockets = ["vcan0", "vcan1"])"; // Invalid config identifier + sdv::SObjectInfo sObjectInfo{}; + sObjectInfo.ssConfig = R"(invalidCanSockets = ["vcan0", "vcan1"])"; // Invalid config identifier CTestCANSocket canComObj; - ASSERT_NO_THROW(canComObj.Initialize(ssObjectConfig.c_str())); + ASSERT_NO_THROW(canComObj.Initialize(sObjectInfo)); EXPECT_EQ(canComObj.GetObjectState(), sdv::EObjectState::initialization_failure); ASSERT_NO_THROW(canComObj.Shutdown()); @@ -478,11 +486,11 @@ TEST_F(CANSocketTest, StressTestWith3Objects) std::atomic_bool stopSendThread = false; std::cout << "Start thread sending messages..." << std::endl; - std::thread thSendThread1(SendThread, std::ref(stopSendThread), std::ref(canComObj1), std::ref(testData1)); + sdv::core::secure_thread thSendThread1(SendThread, std::ref(stopSendThread), std::ref(canComObj1), std::ref(testData1)); std::this_thread::sleep_for(std::chrono::milliseconds(500)); - std::thread thSendThread2(SendThread, std::ref(stopSendThread), std::ref(canComObj2), std::ref(testData2)); + sdv::core::secure_thread thSendThread2(SendThread, std::ref(stopSendThread), std::ref(canComObj2), std::ref(testData2)); std::this_thread::sleep_for(std::chrono::milliseconds(500)); - std::thread thSendThread3(SendThread, std::ref(stopSendThread), std::ref(canComObj3), std::ref(testData3)); + sdv::core::secure_thread thSendThread3(SendThread, std::ref(stopSendThread), std::ref(canComObj3), std::ref(testData3)); std::this_thread::sleep_for(std::chrono::seconds(10)); @@ -702,9 +710,10 @@ TEST_F(CANSocketTest, ManualSendAndReceiveTestOfMulitpleSockets) uint32_t vcan2Index = 6; uint32_t vcan3Index = 1; uint32_t vcan4Index = 5; - sdv::u8string ssConfig = R"(canSockets = ["vcan0", "vcan3", "vcan8", "vcan1", "vcan9", "vcan4", "vcan2"])"; CTestCANSocket canComObj; - ASSERT_NO_THROW(canComObj.Initialize(ssConfig.c_str())); + sdv::SObjectInfo sObjectInfo{}; + sObjectInfo.ssConfig = R"(canSockets = ["vcan0", "vcan3", "vcan8", "vcan1", "vcan9", "vcan4", "vcan2"])"; + ASSERT_NO_THROW(canComObj.Initialize(sObjectInfo)); EXPECT_EQ(canComObj.GetObjectState(), sdv::EObjectState::initialized); // Register a receiver. @@ -772,9 +781,10 @@ TEST_F(CANSocketTest, ManualSendAndReceiveTestWithDifferentDataSizes) uint32_t vcan2Index = 6; uint32_t vcan3Index = 1; uint32_t vcan4Index = 5; - sdv::u8string ssConfig = R"(canSockets = ["vcan0", "vcan3", "vcan8", "vcan1", "vcan9", "vcan4", "vcan2"])"; + sdv::SObjectInfo sObjectInfo{}; + sObjectInfo.ssConfig = R"(canSockets = ["vcan0", "vcan3", "vcan8", "vcan1", "vcan9", "vcan4", "vcan2"])"; CTestCANSocket canComObj; - ASSERT_NO_THROW(canComObj.Initialize(ssConfig.c_str())); + ASSERT_NO_THROW(canComObj.Initialize(sObjectInfo)); EXPECT_EQ(canComObj.GetObjectState(), sdv::EObjectState::initialized); // Register a receiver. diff --git a/tests/unit_tests/toml_parser/CMakeLists.txt b/tests/unit_tests/toml_parser/CMakeLists.txt index 656665f..d61c42c 100644 --- a/tests/unit_tests/toml_parser/CMakeLists.txt +++ b/tests/unit_tests/toml_parser/CMakeLists.txt @@ -12,6 +12,9 @@ # Erik Verhoeven - writing TOML and whitespace preservation #******************************************************************************* +# Define project +project (UnitTest_TOMLParser VERSION 1.0 LANGUAGES CXX) + # Character Reader executable add_executable(UnitTest_TOMLParser "character_reader_tests.cpp" @@ -19,13 +22,17 @@ add_executable(UnitTest_TOMLParser "parser_tests.cpp" "main.cpp" "generate_toml_tests.cpp" - "generate_toml_delete_node.cpp" + "delete_node.cpp" "statement_boundary_detection.cpp" - "miscellaneous_tests.cpp" + "miscellaneous.cpp" "generate_toml_with_transfer.cpp" - "generate_toml_switch_inline.cpp" - "generate_toml_getset_comment.cpp" - "generate_toml_insert_node.cpp" "generate_toml_miscellaneous.cpp" "generate_toml_combine_reduce.cpp") + "getset_comment.cpp" + "insert_node.cpp" + "combine_reduce.cpp" + "comparison_tests.cpp" + "make_inline_standard.cpp" + "indexer_tests.cpp" "cascade_insert_node.cpp") + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") target_link_libraries(UnitTest_TOMLParser GTest::GTest ${CMAKE_THREAD_LIBS_INIT}) if (WIN32) diff --git a/tests/unit_tests/toml_parser/cascade_insert_node.cpp b/tests/unit_tests/toml_parser/cascade_insert_node.cpp new file mode 100644 index 0000000..103e5a3 --- /dev/null +++ b/tests/unit_tests/toml_parser/cascade_insert_node.cpp @@ -0,0 +1,536 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#include + +#include "../../../global/localmemmgr.h" +#include "../../../sdv_services/core/toml_parser/parser_node_toml.h" +#include "../../../sdv_services/core/toml_parser/parser_toml.h" +#include + +TEST(CascadeInsertNode, InsertValueInStandardTable) +{ + // Insert a standard table + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + + // Insert the value into the new table + EXPECT_TRUE(root.InsertValue("", "standard_table.value_int", 10)); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table] +value_int = 10)toml"); + + // Insert the values into the existing table + EXPECT_TRUE(root.InsertValue("", "standard_table.value_str", u8"abc")); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table] +value_int = 10 +value_str = "abc")toml"); + EXPECT_TRUE(root.InsertValue("standard_table.value_int", "standard_table.value_float", 123.456)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table] +value_float = 123.456 +value_int = 10 +value_str = "abc")toml"); +} + +TEST(CascadeInsertNode, InsertTableInStandardTable) +{ + // Insert a standard table + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + EXPECT_TRUE(root.InsertTable("", "standard_table", false)); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table])toml"); + + // Insert a standard table + EXPECT_TRUE(root.InsertTable("", "standard_table.table1", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table.table1])toml"); + + // Insert an inline table before + EXPECT_TRUE(root.InsertTable("standard_table.table1", "standard_table.table2", true)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table] +table2 = {} +[standard_table.table1])toml"); + + // Insert an inline table behind -> this will have to be printed before the standard table + EXPECT_TRUE(root.InsertTable("", "standard_table.table3", true)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table] +table2 = {} +table3 = {} +[standard_table.table1])toml"); + + // Insert a standard table in front -> this will have to be printed behind the inline table relative to the elements of the + // standard_table. + EXPECT_TRUE(root.InsertTable("standard_table.table2", "standard_table.table4", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table] +table2 = {} +table3 = {} +[standard_table.table4] +[standard_table.table1])toml"); +} + +TEST(CascadeInsertNode, InsertArrayInStandardTable) +{ + // Insert a standard table + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + EXPECT_TRUE(root.InsertTable("", "standard_table", false)); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table])toml"); + + // Insert arrays + EXPECT_TRUE(root.InsertArray("", "standard_table.value_array1")); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table] +value_array1 = [])toml"); + EXPECT_TRUE(root.InsertArray("", "standard_table.value_array2")); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table] +value_array1 = [] +value_array2 = [])toml"); + EXPECT_TRUE(root.InsertArray("standard_table.value_array1", "standard_table.value_array3")); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table] +value_array3 = [] +value_array1 = [] +value_array2 = [])toml"); +} + +TEST(CascadeInsertNode, InsertTableArrayInStandardTable) +{ + // Insert a standard table + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + EXPECT_TRUE(root.InsertTable("", "standard_table", false)); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table])toml"); + + // Insert standard table array + EXPECT_TRUE(root.InsertTableArray("", "standard_table.table_array1", + false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[standard_table.table_array1]])toml"); + + // Insert an inline table array before + EXPECT_TRUE(root.InsertTableArray("standard_table.table_array1", "standard_table.table_array2", true)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table] +table_array2 = [{}] +[[standard_table.table_array1]])toml"); + + // Insert an inline table array behind -> this will have to be printed before the standard table array + EXPECT_TRUE(root.InsertTableArray("", "standard_table.table_array3", true)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table] +table_array2 = [{}] +table_array3 = [{}] +[[standard_table.table_array1]])toml"); + + // Insert a standard table array in front -> this will have to be printed behind the inline table array relative to the elements + // of the standard_table. + EXPECT_TRUE(root.InsertTableArray("standard_table.table_array2", "standard_table.table_array4", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table] +table_array2 = [{}] +table_array3 = [{}] +[[standard_table.table_array4]] +[[standard_table.table_array1]])toml"); + + // Add an additional table array entry for table array #4 + EXPECT_TRUE(root.InsertTableArray("", "standard_table.table_array4", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table] +table_array2 = [{}] +table_array3 = [{}] +[[standard_table.table_array4]] +[[standard_table.table_array1]] +[[standard_table.table_array4]])toml"); +} + +TEST(CascadeInsertNode, InsertValueInInlineTable) +{ + // Insert an inline table + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + EXPECT_TRUE(root.InsertTable("", "inline_table", true)); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {})toml"); + + // Insert the values into the table + EXPECT_TRUE(root.InsertValue("", "inline_table.value_int", 10)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {value_int = 10})toml"); + EXPECT_TRUE(root.InsertValue("", "inline_table.value_str", u8"abc")); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {value_int = 10, value_str = "abc"})toml"); + EXPECT_TRUE(root.InsertValue("inline_table.value_int", "inline_table.value_float", 123.456)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {value_float = 123.456, value_int = 10, value_str = "abc"})toml"); +} + +TEST(CascadeInsertNode, InsertTableInInlineTable) +{ + // Insert an inline table + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + EXPECT_TRUE(root.InsertTable("", "inline_table", true)); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {})toml"); + + // Insert a standard table + EXPECT_TRUE(root.InsertTable("", "inline_table.table1", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {table1 = {}})toml"); + + // Insert an inline table before + EXPECT_TRUE(root.InsertTable("inline_table.table1", "inline_table.table2", true)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {table2 = {}, table1 = {}})toml"); + + // Insert an inline table behind -> this will have to be printed before the standard table + EXPECT_TRUE(root.InsertTable("", "inline_table.table3", true)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {table2 = {}, table1 = {}, table3 = {}})toml"); + + // Insert a standard table in front -> this will have to be printed behind the inline table + EXPECT_TRUE(root.InsertTable("inline_table.table2", "inline_table.table4", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {table4 = {}, table2 = {}, table1 = {}, table3 = {}})toml"); +} + +TEST(CascadeInsertNode, InsertArrayInInlineTable) +{ + // Insert an inline table + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + EXPECT_TRUE(root.InsertTable("", "inline_table", + true)); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {})toml"); + + // Insert arrays + EXPECT_TRUE(root.InsertArray("", "inline_table.value_array1")); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {value_array1 = []})toml"); + EXPECT_TRUE(root.InsertArray("", "inline_table.value_array2")); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {value_array1 = [], value_array2 = []})toml"); + EXPECT_TRUE(root.InsertArray("inline_table.value_array1", "inline_table.value_array3")); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {value_array3 = [], value_array1 = [], value_array2 = []})toml"); +} + +TEST(CascadeInsertNode, InsertTableArrayInInlineTable) +{ + // Insert an inline table + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + EXPECT_TRUE(root.InsertTable("", "inline_table", true)); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {})toml"); + + // Insert standard table array + EXPECT_TRUE(root.InsertTableArray("", "inline_table.table_array1", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {table_array1 = [{}]})toml"); + + // Insert an inline table array before + EXPECT_TRUE(root.InsertTableArray("inline_table.table_array1", "inline_table.table_array2", true)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {table_array2 = [{}], table_array1 = [{}]})toml"); + + // Insert an inline table array behind -> this will have to be printed before the standard table array + EXPECT_TRUE(root.InsertTableArray("", "inline_table.table_array3", true)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {table_array2 = [{}], table_array1 = [{}], table_array3 = [{}]})toml"); + + // Insert a standard table array in front -> this will have to be printed behind the inline table array + EXPECT_TRUE(root.InsertTableArray("inline_table.table_array2", "inline_table.table_array4", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, + R"toml(inline_table = {table_array4 = [{}], table_array2 = [{}], table_array1 = [{}], table_array3 = [{}]})toml"); + + // Add an additional table array entry for table array #4 + EXPECT_TRUE(root.InsertTableArray("", "inline_table.table_array4", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, + R"toml(inline_table = {table_array4 = [{}, {}], table_array2 = [{}], table_array1 = [{}], table_array3 = [{}]})toml"); +} + +TEST(CascadeInsertNode, InsertValueInArray) +{ + // Insert an array + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + EXPECT_TRUE(root.InsertArray("", "inline_array")); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [])toml"); + + // Insert the values into the table (with or without name) + EXPECT_TRUE(root.InsertValue("", "inline_array", 10)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [10])toml"); + EXPECT_TRUE(root.InsertValue("", "inline_array", u8"abc")); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [10, "abc"])toml"); + EXPECT_TRUE(root.InsertValue("inline_array[0]", "inline_array", 123.456)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [123.456, 10, "abc"])toml"); +} + +TEST(CascadeInsertNode, InsertTableInArray) +{ + // Insert an array + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + EXPECT_TRUE(root.InsertArray("", "inline_array")); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [])toml"); + + // Insert a standard table + EXPECT_TRUE(root.InsertTable("", "inline_array", false)); + EXPECT_TRUE(root.InsertValue("", "inline_array[0].a", 10)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [{a = 10}])toml"); + + // Insert an inline table before + EXPECT_TRUE(root.InsertTable("inline_array[0]", "inline_array", true)); + EXPECT_TRUE(root.InsertValue("", "inline_array[0].b", 20)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [{b = 20}, {a = 10}])toml"); + + // Insert an inline table behind + EXPECT_TRUE(root.InsertTable("", "inline_array", true)); + EXPECT_TRUE(root.InsertValue("", "inline_array.c", 30)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [{b = 20}, {a = 10}, {c = 30}])toml"); + + // Insert a standard table in front + EXPECT_TRUE(root.InsertTable("inline_array[0]", "inline_array", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_TRUE(root.InsertValue("", "inline_array[0].d", 40)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [{d = 40}, {b = 20}, {a = 10}, {c = 30}])toml"); +} + +TEST(CascadeInsertNode, InsertArrayInArray) +{ + // Insert an array + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + EXPECT_TRUE(root.InsertArray("", "inline_array")); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [])toml"); + + // Insert arrays + EXPECT_TRUE(root.InsertArray("", "inline_array")); + EXPECT_TRUE(root.InsertValue("", "inline_array[0]", 10)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [[10]])toml"); + EXPECT_TRUE(root.InsertArray("", "inline_array")); + EXPECT_TRUE(root.InsertValue("", "inline_array[1]", 20)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [[10], [20]])toml"); + EXPECT_TRUE(root.InsertArray("inline_array[0]", "inline_array")); + EXPECT_TRUE(root.InsertValue("", "inline_array[0]", 30)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [[30], [10], [20]])toml"); +} + +TEST(CascadeInsertNode, InsertTableArrayInArray) +{ + // Insert an array + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + EXPECT_TRUE(root.InsertArray("", "inline_array")); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [])toml"); + + // Insert standard table array + EXPECT_TRUE(root.InsertTableArray("", "inline_array", false)); + EXPECT_TRUE(root.InsertValue("", "inline_array[0].a", 10)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [[{a = 10}]])toml"); + + // Insert an inline table array before + EXPECT_TRUE(root.InsertTableArray("inline_array[0]", "inline_array", true)); + EXPECT_TRUE(root.InsertValue("", "inline_array[0].b", 20)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [[{b = 20}], [{a = 10}]])toml"); + + // Insert an inline table array behind -> this will have to be printed before the standard table array + EXPECT_TRUE(root.InsertTableArray("", "inline_array", true)); + EXPECT_TRUE(root.InsertValue("", "inline_array[999].c", 30)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [[{b = 20}], [{a = 10}], [{c = 30}]])toml"); + + // Insert a standard table array in front -> this will have to be printed behind the inline table array + EXPECT_TRUE(root.InsertTableArray("inline_array[0]", "inline_array", false)); + EXPECT_TRUE(root.InsertValue("", "inline_array[0].d", 40)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [[{d = 40}], [{b = 20}], [{a = 10}], [{c = 30}]])toml"); + + // Add an additional table array entry for table array #4 + EXPECT_TRUE(root.InsertValue("", "inline_array[0].e", 50)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [[{d = 40, e = 50}], [{b = 20}], [{a = 10}], [{c = 30}]])toml"); +} + +TEST(CascadeInsertNode, InsertValueInTableArray) +{ + // Insert a standard table array + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + EXPECT_TRUE(root.InsertTableArray("", "table_array", false)); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]])toml"); + + // Insert the values into the table + EXPECT_TRUE(root.InsertValue("", "table_array.value_int", 10)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +value_int = 10)toml"); + EXPECT_TRUE(root.InsertValue("", "table_array.value_str", u8"abc")); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +value_int = 10 +value_str = "abc")toml"); + EXPECT_TRUE(root.InsertValue("table_array[0]", "table_array.value_float", 123.456)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +value_float = 123.456 +value_int = 10 +value_str = "abc")toml"); +} + +TEST(CascadeInsertNode, InsertTableInTableArray) +{ + // Insert a standard table array + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + EXPECT_TRUE(root.InsertTableArray("", "table_array", false)); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]])toml"); + + // Insert a standard table + EXPECT_TRUE(root.InsertTable("", "table_array.table1", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +[table_array.table1])toml"); + + // Insert an inline table before + EXPECT_TRUE(root.InsertTable("table_array[0]", "table_array.table2", true)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +table2 = {} +[table_array.table1])toml"); + + // Insert an inline table behind -> this will have to be printed before the standard table + EXPECT_TRUE(root.InsertTable("", "table_array.table3", true)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +table2 = {} +table3 = {} +[table_array.table1])toml"); + + // Insert a standard table in front -> this will have to be printed behind the inline table + EXPECT_TRUE(root.InsertTable("table_array[0]", "table_array.table4", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +table2 = {} +table3 = {} +[table_array.table4] +[table_array.table1])toml"); +} + +TEST(CascadeInsertNode, InsertArrayInTableArray) +{ + // Insert a standard table array + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + EXPECT_TRUE(root.InsertTableArray("", "table_array", false)); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]])toml"); + + // Insert arrays + EXPECT_TRUE(root.InsertArray("", "table_array.value_array1")); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +value_array1 = [])toml"); + EXPECT_TRUE(root.InsertArray("", "table_array.value_array2")); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +value_array1 = [] +value_array2 = [])toml"); + EXPECT_TRUE(root.InsertArray("table_array[0]", "table_array.value_array3")); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +value_array3 = [] +value_array1 = [] +value_array2 = [])toml"); +} + +TEST(CascadeInsertNode, InsertTableArrayInTableArray) +{ + // Insert a standard table array + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + EXPECT_TRUE(root.InsertTableArray("", "table_array", false)); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]])toml"); + + // Insert standard table array + EXPECT_TRUE(root.InsertTableArray("", "table_array.table_array1", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +[[table_array.table_array1]])toml"); + + // Insert an inline table array before + EXPECT_TRUE(root.InsertTableArray("table_array[0]", "table_array.table_array2", true)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +table_array2 = [{}] +[[table_array.table_array1]])toml"); + + // Insert an inline table array behind -> this will have to be printed before the standard table array + EXPECT_TRUE(root.InsertTableArray("", "table_array.table_array3", true)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +table_array2 = [{}] +table_array3 = [{}] +[[table_array.table_array1]])toml"); + + // Insert a standard table array in front -> this will have to be printed behind the inline table array + EXPECT_TRUE(root.InsertTableArray("table_array[0]", "table_array.table_array4", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +table_array2 = [{}] +table_array3 = [{}] +[[table_array.table_array4]] +[[table_array.table_array1]])toml"); + + // Add an additional table array entry for table array #4 + EXPECT_TRUE(root.InsertTableArray("", "table_array.table_array4", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +table_array2 = [{}] +table_array3 = [{}] +[[table_array.table_array4]] +[[table_array.table_array1]] +[[table_array.table_array4]])toml"); +} diff --git a/tests/unit_tests/toml_parser/character_reader_tests.cpp b/tests/unit_tests/toml_parser/character_reader_tests.cpp index a5791c3..328b116 100644 --- a/tests/unit_tests/toml_parser/character_reader_tests.cpp +++ b/tests/unit_tests/toml_parser/character_reader_tests.cpp @@ -13,6 +13,7 @@ ********************************************************************************/ #include +#include "../../../global/localmemmgr.h" #include "../../../sdv_services/core/toml_parser/character_reader_utf_8.h" #include "../../../sdv_services/core/toml_parser/exception.h" diff --git a/tests/unit_tests/toml_parser/generate_toml_combine_reduce.cpp b/tests/unit_tests/toml_parser/combine_reduce.cpp similarity index 74% rename from tests/unit_tests/toml_parser/generate_toml_combine_reduce.cpp rename to tests/unit_tests/toml_parser/combine_reduce.cpp index cfa13e4..e979677 100644 --- a/tests/unit_tests/toml_parser/generate_toml_combine_reduce.cpp +++ b/tests/unit_tests/toml_parser/combine_reduce.cpp @@ -12,11 +12,11 @@ ********************************************************************************/ #include - +#include "../../../global/localmemmgr.h" #include "../../../sdv_services/core/toml_parser/parser_node_toml.h" #include "../../../sdv_services/core/toml_parser/parser_toml.h" -TEST(GenerateTOML, CombineRoot) +TEST(CombineReduse, CombineRoot) { toml_parser::CParser parser1(R"toml( val1 = 10 @@ -27,10 +27,10 @@ val1 = 10)toml"); EXPECT_TRUE(parser1.Root().Combine(parser2.Root().Cast())); - std::string ssCombinedTOML = R"code( + std::string ssCombinedTOML = R"toml( val1 = 10 val2 = "20" -val3 = 30.0)code"; +val3 = 30.0)toml"; std::string ssGenerated; EXPECT_NO_THROW(ssGenerated = parser1.GenerateTOML()); @@ -38,7 +38,7 @@ val3 = 30.0)code"; EXPECT_EQ(ssGenerated, ssCombinedTOML); } -TEST(GenerateTOML, CombineRootComments) +TEST(CombineReduse, CombineRootComments) { toml_parser::CParser parser1(R"toml( val1 = 10 # This is value 1 @@ -49,10 +49,10 @@ val1 = 10 # This is again value 1)toml"); EXPECT_TRUE(parser1.Root().Combine(parser2.Root().Cast())); - std::string ssCombinedTOML = R"code( + std::string ssCombinedTOML = R"toml( val1 = 10 # This is value 1 val2 = "20" # This is value 2 -val3 = 30.0 # This is value 3)code"; +val3 = 30.0 # This is value 3)toml"; std::string ssGenerated; EXPECT_NO_THROW(ssGenerated = parser1.GenerateTOML()); @@ -60,7 +60,7 @@ val3 = 30.0 # This is value 3)code"; EXPECT_EQ(ssGenerated, ssCombinedTOML); } -TEST(GenerateTOML, CombineRootwithTable) +TEST(CombineReduse, CombineRootwithTableValues) { toml_parser::CParser parser1(R"toml( val1 = 10 @@ -73,10 +73,10 @@ val1 = 10)toml"); ASSERT_TRUE(ptrTable); EXPECT_TRUE(parser1.Root().Combine(ptrTable->Cast())); - std::string ssCombinedTOML = R"code( + std::string ssCombinedTOML = R"toml( val1 = 10 val2 = "20" -val3 = 30.0)code"; +val3 = 30.0)toml"; std::string ssGenerated; EXPECT_NO_THROW(ssGenerated = parser1.GenerateTOML()); @@ -84,7 +84,55 @@ val3 = 30.0)code"; EXPECT_EQ(ssGenerated, ssCombinedTOML); } -TEST(GenerateTOML, CombineTablewithRoot) +TEST(CombineReduse, CombineRootwithStandardTable) +{ + toml_parser::CParser parser1(R"toml( +val1 = 10 +val2 = "20")toml"); + toml_parser::CParser parser2(R"toml([MyTable] +val3 = 30.0 +val1 = 10)toml"); + + auto ptrTable = &parser2.Root(); + ASSERT_TRUE(ptrTable); + EXPECT_TRUE(parser1.Root().Combine(ptrTable->Cast())); + + std::string ssCombinedTOML = R"toml( +val1 = 10 +val2 = "20" +[MyTable] +val3 = 30.0 +val1 = 10)toml"; + + std::string ssGenerated; + EXPECT_NO_THROW(ssGenerated = parser1.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssCombinedTOML); +} + +TEST(CombineReduse, CombineRootwithInlineTable) +{ + toml_parser::CParser parser1(R"toml( +val1 = 10 +val2 = "20")toml"); + toml_parser::CParser parser2(R"toml(MyTable = {val3 = 30.0, val1 = 10})toml"); + + auto ptrTable = &parser2.Root(); + ASSERT_TRUE(ptrTable); + EXPECT_TRUE(parser1.Root().Combine(ptrTable->Cast())); + + std::string ssCombinedTOML = R"toml( +val1 = 10 +val2 = "20" +MyTable = {val3 = 30.0, val1 = 10})toml"; + + std::string ssGenerated; + EXPECT_NO_THROW(ssGenerated = parser1.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssCombinedTOML); +} + +TEST(CombineReduse, CombineTablewithRoot) { toml_parser::CParser parser1(R"toml([MyTable] val1 = 10 @@ -99,10 +147,10 @@ val1 = 10)toml"); ASSERT_TRUE(ptrTable); EXPECT_TRUE(ptrTable->Combine(parser2.Root().Cast())); - std::string ssCombinedTOML = R"code([MyTable] + std::string ssCombinedTOML = R"toml([MyTable] val1 = 10 val2 = "20" -val3 = 30.0)code"; +val3 = 30.0)toml"; std::string ssGenerated; EXPECT_NO_THROW(ssGenerated = parser1.GenerateTOML()); @@ -110,7 +158,7 @@ val3 = 30.0)code"; EXPECT_EQ(ssGenerated, ssCombinedTOML); } -TEST(GenerateTOML, CombineTablewithDifferentTable) +TEST(CombineReduse, CombineTablewithDifferentTable) { toml_parser::CParser parser1(R"toml([MyTable1] val1 = 10 @@ -129,10 +177,10 @@ val1 = 10)toml"); ASSERT_TRUE(ptrTable2); EXPECT_TRUE(ptrTable1->Combine(ptrTable2)); - std::string ssCombinedTOML = R"code([MyTable1] + std::string ssCombinedTOML = R"toml([MyTable1] val1 = 10 val2 = "20" -val3 = 30.0)code"; +val3 = 30.0)toml"; std::string ssGenerated; EXPECT_NO_THROW(ssGenerated = parser1.GenerateTOML()); @@ -140,7 +188,7 @@ val3 = 30.0)code"; EXPECT_EQ(ssGenerated, ssCombinedTOML); } -TEST(GenerateTOML, CombineTablewithIdenticalTable) +TEST(CombineReduse, CombineTablewithIdenticalTable) { toml_parser::CParser parser1(R"toml([MyTable] val1 = 10 @@ -151,10 +199,10 @@ val1 = 10)toml"); EXPECT_TRUE(parser1.Root().Combine(parser2.Root().Cast())); - std::string ssCombinedTOML = R"code([MyTable] + std::string ssCombinedTOML = R"toml([MyTable] val1 = 10 val2 = "20" -val3 = 30.0)code"; +val3 = 30.0)toml"; std::string ssGenerated; EXPECT_NO_THROW(ssGenerated = parser1.GenerateTOML()); @@ -162,7 +210,7 @@ val3 = 30.0)code"; EXPECT_EQ(ssGenerated, ssCombinedTOML); } -TEST(GenerateTOML, CombineInlineTablewithDifferentStandardTable) +TEST(CombineReduse, CombineInlineTablewithDifferentStandardTable) { toml_parser::CParser parser1(R"toml(MyTable1 = {val1 = 10, val2 = "20"})toml"); toml_parser::CParser parser2(R"toml([MyTable2] @@ -179,7 +227,7 @@ val1 = 10)toml"); ASSERT_TRUE(ptrTable2); EXPECT_TRUE(ptrTable1->Combine(ptrTable2)); - std::string ssCombinedTOML = R"code(MyTable1 = {val1 = 10, val2 = "20", val3 = 30.0})code"; + std::string ssCombinedTOML = R"toml(MyTable1 = {val1 = 10, val2 = "20", val3 = 30.0})toml"; std::string ssGenerated; EXPECT_NO_THROW(ssGenerated = parser1.GenerateTOML()); @@ -187,7 +235,7 @@ val1 = 10)toml"); EXPECT_EQ(ssGenerated, ssCombinedTOML); } -TEST(GenerateTOML, CombineInlineTablewithIdenticalStandardTable) +TEST(CombineReduse, CombineInlineTablewithIdenticalStandardTable) { toml_parser::CParser parser1(R"toml(MyTable = {val1 = 10, val2 = "20"})toml"); toml_parser::CParser parser2(R"toml([MyTable] @@ -196,7 +244,7 @@ val1 = 10)toml"); EXPECT_TRUE(parser1.Root().Combine(parser2.Root().Cast())); - std::string ssCombinedTOML = R"code(MyTable = {val1 = 10, val2 = "20", val3 = 30.0})code"; + std::string ssCombinedTOML = R"toml(MyTable = {val1 = 10, val2 = "20", val3 = 30.0})toml"; std::string ssGenerated; EXPECT_NO_THROW(ssGenerated = parser1.GenerateTOML()); @@ -204,7 +252,7 @@ val1 = 10)toml"); EXPECT_EQ(ssGenerated, ssCombinedTOML); } -TEST(GenerateTOML, CombineStandardTablewithDifferentInlineTable) +TEST(CombineReduse, CombineStandardTablewithDifferentInlineTable) { toml_parser::CParser parser1(R"toml([MyTable1] val1 = 10 @@ -221,10 +269,10 @@ val2 = "20")toml"); ASSERT_TRUE(ptrTable2); EXPECT_TRUE(ptrTable1->Combine(ptrTable2)); - std::string ssCombinedTOML = R"code([MyTable1] + std::string ssCombinedTOML = R"toml([MyTable1] val1 = 10 val2 = "20" -val3 = 30.0)code"; +val3 = 30.0)toml"; std::string ssGenerated; EXPECT_NO_THROW(ssGenerated = parser1.GenerateTOML()); @@ -232,7 +280,7 @@ val3 = 30.0)code"; EXPECT_EQ(ssGenerated, ssCombinedTOML); } -TEST(GenerateTOML, CombineStandardTablewithIdenticalInlineTable) +TEST(CombineReduse, CombineStandardTablewithIdenticalInlineTable) { toml_parser::CParser parser1(R"toml([MyTable] val1 = 10 @@ -241,10 +289,10 @@ val2 = "20")toml"); EXPECT_TRUE(parser1.Root().Combine(parser2.Root().Cast())); - std::string ssCombinedTOML = R"code([MyTable] + std::string ssCombinedTOML = R"toml([MyTable] val1 = 10 val2 = "20" -val3 = 30.0)code"; +val3 = 30.0)toml"; std::string ssGenerated; EXPECT_NO_THROW(ssGenerated = parser1.GenerateTOML()); @@ -252,7 +300,7 @@ val3 = 30.0)code"; EXPECT_EQ(ssGenerated, ssCombinedTOML); } -TEST(GenerateTOML, CombineInlineTablewithDifferentInlineTable) +TEST(CombineReduse, CombineInlineTablewithDifferentInlineTable) { toml_parser::CParser parser1(R"toml(MyTable1 = {val1 = 10, val2 = "20"})toml"); toml_parser::CParser parser2(R"toml(MyTable2 = {val3 = 30.0, val1 = 10})toml"); @@ -267,7 +315,7 @@ TEST(GenerateTOML, CombineInlineTablewithDifferentInlineTable) ASSERT_TRUE(ptrTable2); EXPECT_TRUE(ptrTable1->Combine(ptrTable2)); - std::string ssCombinedTOML = R"code(MyTable1 = {val1 = 10, val2 = "20", val3 = 30.0})code"; + std::string ssCombinedTOML = R"toml(MyTable1 = {val1 = 10, val2 = "20", val3 = 30.0})toml"; std::string ssGenerated; EXPECT_NO_THROW(ssGenerated = parser1.GenerateTOML()); @@ -275,14 +323,14 @@ TEST(GenerateTOML, CombineInlineTablewithDifferentInlineTable) EXPECT_EQ(ssGenerated, ssCombinedTOML); } -TEST(GenerateTOML, CombineInlineTablewithIdenticalInlineTable) +TEST(CombineReduse, CombineInlineTablewithIdenticalInlineTable) { toml_parser::CParser parser1(R"toml(MyTable = {val1 = 10, val2 = "20"})toml"); toml_parser::CParser parser2(R"toml(MyTable = {val3 = 30.0, val1 = 10})toml"); EXPECT_TRUE(parser1.Root().Combine(parser2.Root().Cast())); - std::string ssCombinedTOML = R"code(MyTable = {val1 = 10, val2 = "20", val3 = 30.0})code"; + std::string ssCombinedTOML = R"toml(MyTable = {val1 = 10, val2 = "20", val3 = 30.0})toml"; std::string ssGenerated; EXPECT_NO_THROW(ssGenerated = parser1.GenerateTOML()); @@ -290,7 +338,7 @@ TEST(GenerateTOML, CombineInlineTablewithIdenticalInlineTable) EXPECT_EQ(ssGenerated, ssCombinedTOML); } -TEST(GenerateTOML, CombineDifferentArrays) +TEST(CombineReduse, CombineDifferentArrays) { toml_parser::CParser parser1(R"toml( val1 = [10, 20] @@ -301,10 +349,10 @@ val1 = [70, 80])toml"); EXPECT_TRUE(parser1.Root().Combine(parser2.Root().Cast())); - std::string ssCombinedTOML = R"code( + std::string ssCombinedTOML = R"toml( val1 = [70, 80] val2 = ["30", "40"] -val3 = [50.0, 60.0])code"; +val3 = [50.0, 60.0])toml"; std::string ssGenerated; EXPECT_NO_THROW(ssGenerated = parser1.GenerateTOML()); @@ -312,12 +360,13 @@ val3 = [50.0, 60.0])code"; EXPECT_EQ(ssGenerated, ssCombinedTOML); } -TEST(GenerateTOML, CombineDifferentTableArrays) +TEST(CombineReduse, CombineDifferentTableArrays) { toml_parser::CParser parser1(R"toml([[table_array1]] val1 = [10, 20] [[table_array1]] val2 = ["30", "40"])toml"); + toml_parser::CParser parser2(R"toml( [[table_array2]] val3 = [50.0, 60.0] @@ -326,14 +375,14 @@ val1 = [70, 80])toml"); EXPECT_TRUE(parser1.Root().Combine(parser2.Root().Cast())); - std::string ssCombinedTOML = R"code([[table_array1]] + std::string ssCombinedTOML = R"toml([[table_array1]] val1 = [10, 20] [[table_array1]] val2 = ["30", "40"] [[table_array2]] val3 = [50.0, 60.0] [[table_array2]] -val1 = [70, 80])code"; +val1 = [70, 80])toml"; std::string ssGenerated; EXPECT_NO_THROW(ssGenerated = parser1.GenerateTOML()); @@ -341,7 +390,7 @@ val1 = [70, 80])code"; EXPECT_EQ(ssGenerated, ssCombinedTOML); } -TEST(GenerateTOML, ReduceRoot) +TEST(CombineReduse, ReduceRoot) { toml_parser::CParser parser1(R"toml( val1 = 10 @@ -353,9 +402,8 @@ val1 = 10)toml"); EXPECT_TRUE(parser1.Root().Reduce(parser2.Root().Cast())); - std::string ssCombinedTOML = R"code( -val2 = "20" -val3 = 30.0)code"; + std::string ssCombinedTOML = R"toml(val2 = "20" +val3 = 30.0)toml"; std::string ssGenerated; EXPECT_NO_THROW(ssGenerated = parser1.GenerateTOML()); @@ -363,7 +411,7 @@ val3 = 30.0)code"; EXPECT_EQ(ssGenerated, ssCombinedTOML); } -TEST(GenerateTOML, ReduceRootComments) +TEST(CombineReduse, ReduceRootComments) { toml_parser::CParser parser1(R"toml( val1 = 10 # This is value 1 @@ -375,9 +423,8 @@ val1 = 10 # This also is value 1)toml"); EXPECT_TRUE(parser1.Root().Reduce(parser2.Root().Cast())); - std::string ssCombinedTOML = R"code( -val2 = "20" # This is value 2 -val3 = 30.0 # This is value 3)code"; + std::string ssCombinedTOML = R"toml(val2 = "20" # This is value 2 +val3 = 30.0 # This is value 3)toml"; std::string ssGenerated; EXPECT_NO_THROW(ssGenerated = parser1.GenerateTOML()); @@ -385,7 +432,7 @@ val3 = 30.0 # This is value 3)code"; EXPECT_EQ(ssGenerated, ssCombinedTOML); } -TEST(GenerateTOML, ReduceTable) +TEST(CombineReduse, ReduceTable) { toml_parser::CParser parser1(R"toml([my_table] val1 = 10 @@ -397,9 +444,9 @@ val1 = 10)toml"); EXPECT_TRUE(parser1.Root().Reduce(parser2.Root().Cast())); - std::string ssCombinedTOML = R"code([my_table] + std::string ssCombinedTOML = R"toml([my_table] val2 = "20" -val3 = 30.0)code"; +val3 = 30.0)toml"; std::string ssGenerated; EXPECT_NO_THROW(ssGenerated = parser1.GenerateTOML()); @@ -407,7 +454,7 @@ val3 = 30.0)code"; EXPECT_EQ(ssGenerated, ssCombinedTOML); } -TEST(GenerateTOML, ReduceStandardTableWithInlineTable) +TEST(CombineReduse, ReduceStandardTableWithInlineTable) { toml_parser::CParser parser1(R"toml([my_table] val1 = 10 @@ -417,9 +464,9 @@ val3 = 30.0)toml"); EXPECT_TRUE(parser1.Root().Reduce(parser2.Root().Cast())); - std::string ssCombinedTOML = R"code([my_table] + std::string ssCombinedTOML = R"toml([my_table] val2 = "20" -val3 = 30.0)code"; +val3 = 30.0)toml"; std::string ssGenerated; EXPECT_NO_THROW(ssGenerated = parser1.GenerateTOML()); @@ -427,7 +474,7 @@ val3 = 30.0)code"; EXPECT_EQ(ssGenerated, ssCombinedTOML); } -TEST(GenerateTOML, ReduceInlineTableWithStandardTable) +TEST(CombineReduse, ReduceInlineTableWithStandardTable) { toml_parser::CParser parser1(R"toml(my_table = {val1 = 10, val2 = "20", val3 = 30.0})toml"); toml_parser::CParser parser2(R"toml([my_table] @@ -436,7 +483,7 @@ val1 = 10)toml"); EXPECT_TRUE(parser1.Root().Reduce(parser2.Root().Cast())); - std::string ssCombinedTOML = R"code(my_table = { val2 = "20", val3 = 30.0})code"; + std::string ssCombinedTOML = R"toml(my_table = { val2 = "20", val3 = 30.0})toml"; std::string ssGenerated; EXPECT_NO_THROW(ssGenerated = parser1.GenerateTOML()); @@ -444,14 +491,14 @@ val1 = 10)toml"); EXPECT_EQ(ssGenerated, ssCombinedTOML); } -TEST(GenerateTOML, ReduceInlineTable) +TEST(CombineReduse, ReduceInlineTable) { toml_parser::CParser parser1(R"toml(my_table = {val1 = 10, val2 = "20", val3 = 30.0})toml"); toml_parser::CParser parser2(R"toml(my_table = {val3 = 35.0, val1 = 10})toml"); EXPECT_TRUE(parser1.Root().Reduce(parser2.Root().Cast())); - std::string ssCombinedTOML = R"code(my_table = { val2 = "20", val3 = 30.0})code"; + std::string ssCombinedTOML = R"toml(my_table = { val2 = "20", val3 = 30.0})toml"; std::string ssGenerated; EXPECT_NO_THROW(ssGenerated = parser1.GenerateTOML()); @@ -459,12 +506,12 @@ TEST(GenerateTOML, ReduceInlineTable) EXPECT_EQ(ssGenerated, ssCombinedTOML); } -TEST(GenerateTOML, ReduceDifferentArrays) +TEST(CombineReduse, ReduceDifferentArrays) { - toml_parser::CParser parser1(R"code( + toml_parser::CParser parser1(R"toml( val1 = [10, 20] val2 = ["30", "40"] -val3 = [50.0, 60.0])code"); +val3 = [50.0, 60.0])toml"); toml_parser::CParser parser2(R"toml( val3 = [50.0, 60.0] val1 = [70, 80])toml"); @@ -482,16 +529,16 @@ val2 = ["30", "40"] EXPECT_EQ(ssGenerated, ssCombinedTOML); } -TEST(GenerateTOML, ReduceDifferentTableArrays) +TEST(CombineReduse, ReduceDifferentTableArrays) { - toml_parser::CParser parser1(R"code([[table_array1]] + toml_parser::CParser parser1(R"toml([[table_array1]] val1 = [10, 20] [[table_array1]] val2 = ["30", "40"] [[table_array2]] val3 = [50.0, 60.0] [[table_array2]] -val1 = [70, 80])code"); +val1 = [70, 80])toml"); toml_parser::CParser parser2(R"toml( [[table_array1]] val2 = ["30", "40"] diff --git a/tests/unit_tests/toml_parser/comparison_tests.cpp b/tests/unit_tests/toml_parser/comparison_tests.cpp new file mode 100644 index 0000000..bdca9d7 --- /dev/null +++ b/tests/unit_tests/toml_parser/comparison_tests.cpp @@ -0,0 +1,386 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial implementation + ********************************************************************************/ + +#include +#include +#include +#include "../../../global/localmemmgr.h" +#include +#include "../../../sdv_services/core/toml_parser/exception.h" +#include "../../../sdv_services/core/toml_parser/miscellaneous.h" +#include "../../../sdv_services/core/toml_parser/parser_toml.h" + +bool CompareTest(const std::string& rssToml1, const std::string& rssToml2, + uint32_t uiCompareFlags = static_cast(sdv::toml::ECompareFlags::compare_ignore_all)) +{ + try + { + toml_parser::CParser parser1(rssToml1); + toml_parser::CParser parser2(rssToml2); + sdv::toml::CNodeCollection collection1(&parser1.Root()); + sdv::toml::CNodeCollection collection2(&parser2.Root()); + return sdv::toml::internal::CompareNodes(collection1, collection2, uiCompareFlags) == + sdv::toml::ECompareResult::compare_identical; + } + catch (const toml_parser::XTOMLParseException&) + { + return false; + } +} + +TEST(Comparison, CompareInvalid) +{ + EXPECT_FALSE(CompareTest("var = A", "")); + EXPECT_FALSE(CompareTest("[[test]", "")); + EXPECT_FALSE(CompareTest(R"toml([test] +test1 = "this is invalid')toml", "")); +} + +TEST(Comparison, CompareEmpty) +{ + // Identical + uint32_t uiFlags = 0u; + EXPECT_FALSE(CompareTest("var = 1", "", uiFlags)); + EXPECT_FALSE(CompareTest("", "var = 2", uiFlags)); + EXPECT_TRUE(CompareTest("", "", uiFlags)); + EXPECT_FALSE(CompareTest("", R"toml( +)toml", uiFlags)); + EXPECT_FALSE(CompareTest("", R"toml(# This is a comment)toml", uiFlags)); + + // Ignore whitespace + uiFlags = static_cast(sdv::toml::ECompareFlags::compare_ignore_whitespace); + EXPECT_FALSE(CompareTest("var = 1", "", uiFlags)); + EXPECT_FALSE(CompareTest("", "var = 2", uiFlags)); + EXPECT_TRUE(CompareTest("", "", uiFlags)); + EXPECT_TRUE(CompareTest("", R"toml( +)toml", uiFlags)); + EXPECT_FALSE(CompareTest("", R"toml(# This is a comment)toml", uiFlags)); + + // Ignore comments + uiFlags = static_cast(sdv::toml::ECompareFlags::compare_ignore_comments); + EXPECT_FALSE(CompareTest("var = 1", "", uiFlags)); + EXPECT_FALSE(CompareTest("", "var = 2", uiFlags)); + EXPECT_TRUE(CompareTest("", "", uiFlags)); + EXPECT_TRUE(CompareTest("", R"toml( +)toml", uiFlags)); + EXPECT_TRUE(CompareTest("", R"toml(# This is a comment)toml", uiFlags)); +} + +TEST(Comparison, CompareValues) +{ + std::string ssVal = "val = 1"; + std::string ssVal2 = "val = 2"; + std::string ssValSpace = " val = 1"; + std::string ssValCommentsBefore = R"toml(# comment + +# more comment + val = 1)toml"; + std::string ssValCommentsBehind = R"toml( val = 1 # comment +# more comment + +# lots more comment)toml"; + + // Identical + uint32_t uiFlags = 0u; + EXPECT_TRUE(CompareTest(ssVal, ssVal, uiFlags)); + EXPECT_FALSE(CompareTest(ssVal, ssVal2, uiFlags)); + EXPECT_FALSE(CompareTest(ssVal, ssValSpace, uiFlags)); + EXPECT_FALSE(CompareTest(ssVal, ssValCommentsBefore, uiFlags)); + EXPECT_FALSE(CompareTest(ssVal, ssValCommentsBehind, uiFlags)); + + // Ignore whitespace + uiFlags = static_cast(sdv::toml::ECompareFlags::compare_ignore_whitespace); + EXPECT_TRUE(CompareTest(ssVal, ssVal, uiFlags)); + EXPECT_FALSE(CompareTest(ssVal, ssVal2, uiFlags)); + EXPECT_TRUE(CompareTest(ssVal, ssValSpace, uiFlags)); + EXPECT_FALSE(CompareTest(ssVal, ssValCommentsBefore, uiFlags)); + EXPECT_FALSE(CompareTest(ssVal, ssValCommentsBehind, uiFlags)); + + // Ignore comments + uiFlags = static_cast(sdv::toml::ECompareFlags::compare_ignore_comments); + EXPECT_TRUE(CompareTest(ssVal, ssVal, uiFlags)); + EXPECT_FALSE(CompareTest(ssVal, ssVal2, uiFlags)); + EXPECT_TRUE(CompareTest(ssVal, ssValSpace, uiFlags)); + EXPECT_TRUE(CompareTest(ssVal, ssValCommentsBefore, uiFlags)); + EXPECT_TRUE(CompareTest(ssVal, ssValCommentsBehind, uiFlags)); + + // Ignore all + uiFlags = static_cast(sdv::toml::ECompareFlags::compare_ignore_all); + EXPECT_TRUE(CompareTest(ssVal, ssVal, uiFlags)); + EXPECT_FALSE(CompareTest(ssVal, ssVal2, uiFlags)); + EXPECT_TRUE(CompareTest(ssVal, ssValSpace, uiFlags)); + EXPECT_TRUE(CompareTest(ssVal, ssValCommentsBefore, uiFlags)); + EXPECT_TRUE(CompareTest(ssVal, ssValCommentsBehind, uiFlags)); +} + +TEST(Comparison, CompareTables) +{ + std::string ssStandardTable = R"toml([table1] +val = 1)toml"; + std::string ssStandardTable2 = R"toml([table2] +val = 1)toml"; + std::string ssStandardTable1_3 = R"toml([table1] +val = 3)toml"; + std::string ssInlineTable = R"toml(table1 = {val = 1})toml"; + std::string ssTableSpace = R"toml( [table1] + + val = 1)toml"; + std::string ssTableCommentsBefore = R"toml(# comment + +# more comment + [table1] +val = 1)toml"; + std::string ssTableCommentsBehind = R"toml( [table1] # comment +val = 1 # comment +# more comment + +# lots more comment)toml"; + + // Identical + uint32_t uiFlags = 0u; + EXPECT_TRUE(CompareTest(ssStandardTable, ssStandardTable, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTable, ssStandardTable2, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTable, ssStandardTable1_3, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTable, ssInlineTable, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTable, ssTableSpace, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTable, ssTableCommentsBefore, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTable, ssTableCommentsBehind, uiFlags)); + + // Ignore whitespace + uiFlags = static_cast(sdv::toml::ECompareFlags::compare_ignore_whitespace); + EXPECT_TRUE(CompareTest(ssStandardTable, ssStandardTable, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTable, ssStandardTable2, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTable, ssStandardTable1_3, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTable, ssInlineTable, uiFlags)); + EXPECT_TRUE(CompareTest(ssStandardTable, ssTableSpace, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTable, ssTableCommentsBefore, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTable, ssTableCommentsBehind, uiFlags)); + + // Ignore comments + uiFlags = static_cast(sdv::toml::ECompareFlags::compare_ignore_comments); + EXPECT_TRUE(CompareTest(ssStandardTable, ssStandardTable, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTable, ssStandardTable2, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTable, ssStandardTable1_3, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTable, ssInlineTable, uiFlags)); + EXPECT_TRUE(CompareTest(ssStandardTable, ssTableSpace, uiFlags)); + EXPECT_TRUE(CompareTest(ssStandardTable, ssTableCommentsBefore, uiFlags)); + EXPECT_TRUE(CompareTest(ssStandardTable, ssTableCommentsBehind, uiFlags)); + + // Ignore inline + uiFlags = static_cast(sdv::toml::ECompareFlags::compare_ignore_inline); + EXPECT_TRUE(CompareTest(ssStandardTable, ssStandardTable, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTable, ssStandardTable2, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTable, ssStandardTable1_3, uiFlags)); + EXPECT_TRUE(CompareTest(ssStandardTable, ssInlineTable, uiFlags)); + EXPECT_TRUE(CompareTest(ssStandardTable, ssTableSpace, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTable, ssTableCommentsBefore, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTable, ssTableCommentsBehind, uiFlags)); + + // Ignore all + uiFlags = static_cast(sdv::toml::ECompareFlags::compare_ignore_all); + EXPECT_TRUE(CompareTest(ssStandardTable, ssStandardTable, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTable, ssStandardTable2, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTable, ssStandardTable1_3, uiFlags)); + EXPECT_TRUE(CompareTest(ssStandardTable, ssInlineTable, uiFlags)); + EXPECT_TRUE(CompareTest(ssStandardTable, ssTableSpace, uiFlags)); + EXPECT_TRUE(CompareTest(ssStandardTable, ssTableCommentsBefore, uiFlags)); + EXPECT_TRUE(CompareTest(ssStandardTable, ssTableCommentsBehind, uiFlags)); +} + +TEST(Comparison, CompareArrays) +{ + std::string ssStandardArray = R"toml(array1 = [123, 456])toml"; + std::string ssStandardArray2 = R"toml(array2 = [789, 543])toml"; + std::string ssArraySpace = R"toml( array1 = [ + 123, + 456 ])toml"; + std::string ssArrayCommentsBefore = R"toml(# comment +array1 = +# more comment + [ 123, # xyz +456])toml"; + std::string ssArrayCommentsBehind = R"toml( array1 = [123, 456] # comment +# more comment + +# lots more comment)toml"; + + // Identical + uint32_t uiFlags = 0u; + EXPECT_TRUE(CompareTest(ssStandardArray, ssStandardArray, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardArray, ssStandardArray2, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardArray, ssArraySpace, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardArray, ssArrayCommentsBefore, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardArray, ssArrayCommentsBehind, uiFlags)); + + // Ignore whitespace + uiFlags = static_cast(sdv::toml::ECompareFlags::compare_ignore_whitespace); + EXPECT_TRUE(CompareTest(ssStandardArray, ssStandardArray, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardArray, ssStandardArray2, uiFlags)); + EXPECT_TRUE(CompareTest(ssStandardArray, ssArraySpace, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardArray, ssArrayCommentsBefore, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardArray, ssArrayCommentsBehind, uiFlags)); + + // Ignore comments + uiFlags = static_cast(sdv::toml::ECompareFlags::compare_ignore_comments); + EXPECT_TRUE(CompareTest(ssStandardArray, ssStandardArray, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardArray, ssStandardArray2, uiFlags)); + EXPECT_TRUE(CompareTest(ssStandardArray, ssArraySpace, uiFlags)); + EXPECT_TRUE(CompareTest(ssStandardArray, ssArrayCommentsBefore, uiFlags)); + EXPECT_TRUE(CompareTest(ssStandardArray, ssArrayCommentsBehind, uiFlags)); + + // Ignore all + uiFlags = static_cast(sdv::toml::ECompareFlags::compare_ignore_all); + EXPECT_TRUE(CompareTest(ssStandardArray, ssStandardArray, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardArray, ssStandardArray2, uiFlags)); + EXPECT_TRUE(CompareTest(ssStandardArray, ssArraySpace, uiFlags)); + EXPECT_TRUE(CompareTest(ssStandardArray, ssArrayCommentsBefore, uiFlags)); + EXPECT_TRUE(CompareTest(ssStandardArray, ssArrayCommentsBehind, uiFlags)); +} + +TEST(Comparison, CompareTableArrays) +{ + std::string ssStandardTableArray = R"toml([[tableArray1]] +val = 1)toml"; + std::string ssStandardTableArray2 = R"toml([[tableArray2]] +val = 1)toml"; + std::string ssStandardTableArray1_3 = R"toml([[tableArray1]] +val = 3)toml"; + std::string ssInlineTableArray = R"toml(tableArray1 = [{val = 1}])toml"; + std::string ssTableSpaceArray = R"toml( [[tableArray1]] + + val = 1)toml"; + std::string ssTableArrayCommentsBefore = R"toml(# comment + +# more comment + [[tableArray1]] +val = 1)toml"; + std::string ssTableArrayCommentsBehind = R"toml( [[tableArray1]] # comment +val = 1 # comment +# more comment + +# lots more comment)toml"; + + // Identical + uint32_t uiFlags = 0u; + EXPECT_TRUE(CompareTest(ssStandardTableArray, ssStandardTableArray, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTableArray, ssStandardTableArray2, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTableArray, ssStandardTableArray1_3, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTableArray, ssInlineTableArray, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTableArray, ssTableSpaceArray, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTableArray, ssTableArrayCommentsBefore, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTableArray, ssTableArrayCommentsBehind, uiFlags)); + + // Ignore whitespace + uiFlags = static_cast(sdv::toml::ECompareFlags::compare_ignore_whitespace); + EXPECT_TRUE(CompareTest(ssStandardTableArray, ssStandardTableArray, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTableArray, ssStandardTableArray2, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTableArray, ssStandardTableArray1_3, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTableArray, ssInlineTableArray, uiFlags)); + EXPECT_TRUE(CompareTest(ssStandardTableArray, ssTableSpaceArray, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTableArray, ssTableArrayCommentsBefore, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTableArray, ssTableArrayCommentsBehind, uiFlags)); + + // Ignore comments + uiFlags = static_cast(sdv::toml::ECompareFlags::compare_ignore_comments); + EXPECT_TRUE(CompareTest(ssStandardTableArray, ssStandardTableArray, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTableArray, ssStandardTableArray2, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTableArray, ssStandardTableArray1_3, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTableArray, ssInlineTableArray, uiFlags)); + EXPECT_TRUE(CompareTest(ssStandardTableArray, ssTableSpaceArray, uiFlags)); + EXPECT_TRUE(CompareTest(ssStandardTableArray, ssTableArrayCommentsBefore, uiFlags)); + EXPECT_TRUE(CompareTest(ssStandardTableArray, ssTableArrayCommentsBehind, uiFlags)); + + // Ignore inline + uiFlags = static_cast(sdv::toml::ECompareFlags::compare_ignore_inline); + EXPECT_TRUE(CompareTest(ssStandardTableArray, ssStandardTableArray, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTableArray, ssStandardTableArray2, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTableArray, ssStandardTableArray1_3, uiFlags)); + EXPECT_TRUE(CompareTest(ssStandardTableArray, ssInlineTableArray, uiFlags)); + EXPECT_TRUE(CompareTest(ssStandardTableArray, ssTableSpaceArray, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTableArray, ssTableArrayCommentsBefore, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTableArray, ssTableArrayCommentsBehind, uiFlags)); + + // Ignore all + uiFlags = static_cast(sdv::toml::ECompareFlags::compare_ignore_all); + EXPECT_TRUE(CompareTest(ssStandardTableArray, ssStandardTableArray, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTableArray, ssStandardTableArray2, uiFlags)); + EXPECT_FALSE(CompareTest(ssStandardTableArray, ssStandardTableArray1_3, uiFlags)); + EXPECT_TRUE(CompareTest(ssStandardTableArray, ssInlineTableArray, uiFlags)); + EXPECT_TRUE(CompareTest(ssStandardTableArray, ssTableSpaceArray, uiFlags)); + EXPECT_TRUE(CompareTest(ssStandardTableArray, ssTableArrayCommentsBefore, uiFlags)); + EXPECT_TRUE(CompareTest(ssStandardTableArray, ssTableArrayCommentsBehind, uiFlags)); +} + +TEST(Comparison, CompareComplex) +{ + std::string ssToml1 = R"toml(# This is a complex TOML file + +# Top value description +[Top] +value1 = 1 # Value 1 +valueA = "A" # Value A +arrayBCD = [ "B", # Array element B + "C", # Array element C + "D" ] # Array element D + +# Table 1 +[Top.table1] +value2 = 2 # Value 2 + +# Table array in table 1 +[[Top.table1.table_array]] # Table array +valueE = "E" + +[[Top.table1.table_array]] # Table array +valueF = "F" + +[Top.table1.other_table] +inline_table = { x = "X", y = "Y", z = "Z" } # Inline table + +[[Top.table1.table_array]] # Table array +valueG = "G" +)toml"; + + std::string ssToml2 = R"toml( +[Top] + value1 = 1 + valueA = "A" + arrayBCD = ["B", "C", "D" ] + table1 = { value2 = 2, table_array = [{valueE = "E"}, {valueF = "F"}, {valueG = "G"}], other_table = { inline_table = {x = "X", y = "Y", z = "Z"}}} # Mega table +)toml"; + + // Identical + uint32_t uiFlags = 0u; + EXPECT_TRUE(CompareTest(ssToml1, ssToml1, uiFlags)); + EXPECT_FALSE(CompareTest(ssToml1, ssToml2, uiFlags)); + + // Ignore whitespace + uiFlags = static_cast(sdv::toml::ECompareFlags::compare_ignore_whitespace); + EXPECT_TRUE(CompareTest(ssToml1, ssToml1, uiFlags)); + EXPECT_FALSE(CompareTest(ssToml1, ssToml2, uiFlags)); + + // Ignore comments + uiFlags = static_cast(sdv::toml::ECompareFlags::compare_ignore_comments); + EXPECT_TRUE(CompareTest(ssToml1, ssToml1, uiFlags)); + EXPECT_FALSE(CompareTest(ssToml1, ssToml2, uiFlags)); + + // Ignore inline + uiFlags = static_cast(sdv::toml::ECompareFlags::compare_ignore_inline); + EXPECT_TRUE(CompareTest(ssToml1, ssToml1, uiFlags)); + EXPECT_FALSE(CompareTest(ssToml1, ssToml2, uiFlags)); + + // Ignore all + uiFlags = static_cast(sdv::toml::ECompareFlags::compare_ignore_all); + EXPECT_TRUE(CompareTest(ssToml1, ssToml1, uiFlags)); + EXPECT_TRUE(CompareTest(ssToml1, ssToml2, uiFlags)); +} \ No newline at end of file diff --git a/tests/unit_tests/toml_parser/generate_toml_delete_node.cpp b/tests/unit_tests/toml_parser/delete_node.cpp similarity index 76% rename from tests/unit_tests/toml_parser/generate_toml_delete_node.cpp rename to tests/unit_tests/toml_parser/delete_node.cpp index 8edccec..3496c70 100644 --- a/tests/unit_tests/toml_parser/generate_toml_delete_node.cpp +++ b/tests/unit_tests/toml_parser/delete_node.cpp @@ -12,7 +12,7 @@ ********************************************************************************/ #include - +#include "../../../global/localmemmgr.h" #include "../../../sdv_services/core/toml_parser/parser_node_toml.h" #include "../../../sdv_services/core/toml_parser/parser_toml.h" @@ -45,321 +45,315 @@ * @param[in] rssOuput Reference to the expected ouput. * @return Returns 'true' on success. */ -bool TestDelete(const std::string& rssTOMLInput, const std::string& rssKey, const std::string& rssOutput) +std::string DeleteNode(const std::string& rssTOMLInput, const std::string& rssKey) { toml_parser::CParser parser; bool bRes = true; EXPECT_NO_THROW(bRes = parser.Process(rssTOMLInput)); EXPECT_TRUE(bRes); - if (!bRes) return bRes; + if (!bRes) return {}; auto ptrNode = parser.Root().Direct(rssKey); EXPECT_TRUE(ptrNode); - if (!ptrNode) return false; + if (!ptrNode) return {}; EXPECT_TRUE(bRes = ptrNode->DeleteNode()); - if (!bRes) return bRes; - std::string ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, rssOutput); - if (ssTOML != rssOutput) return false; - return true; + if (!bRes) return {}; + return parser.GenerateTOML(); }; -TEST(TOMLDeleteNode, DeleteValues) +TEST(DeleteNode, DeleteValues) { // Delete a key from the begin (whitespace reduced) - EXPECT_TRUE(TestDelete(R"toml(key = 10 # value key + EXPECT_EQ(DeleteNode(R"toml(key = 10 # value key bare_key = "value" # value bare_key bare-key = false # value bare-key)toml", - "key", + "key"), R"toml(bare_key = "value" # value bare_key -bare-key = false # value bare-key)toml")); +bare-key = false # value bare-key)toml"); // Delete a key from the begin - EXPECT_TRUE(TestDelete(R"toml( + EXPECT_EQ(DeleteNode(R"toml( key = 10 # value key bare_key = "value" # value bare_key bare-key = false # value bare-key )toml", - "key", - R"toml( -bare_key = "value" # value bare_key + "key"), + R"toml(bare_key = "value" # value bare_key bare-key = false # value bare-key -)toml")); +)toml"); // Delete a key from the middle - EXPECT_TRUE(TestDelete(R"toml( + EXPECT_EQ(DeleteNode(R"toml( key = 10 # value key bare_key = "value" # value bare_key bare-key = false # value bare-key )toml", - "bare_key", + "bare_key"), R"toml( key = 10 # value key bare-key = false # value bare-key -)toml")); +)toml"); // Delete a key from the end - EXPECT_TRUE(TestDelete(R"toml( + EXPECT_EQ(DeleteNode(R"toml( key = 10 # value key bare_key = "value" # value bare_key bare-key = false # value bare-key )toml", - "bare-key", + "bare-key"), R"toml( key = 10 # value key bare_key = "value" # value bare_key -)toml")); +)toml"); } -TEST(TOMLDeleteNode, DeleteInlineTableValues) +TEST(DeleteNode, DeleteInlineTableValues) { // Delete key from the inline table - EXPECT_TRUE(TestDelete(R"toml( + EXPECT_EQ(DeleteNode(R"toml( key = 10 bare_key = "value" bare-key = false 1234 = {x = 0, y = 1, z = 2, str = "abc"} )toml", - "1234.y", + "1234.y"), R"toml( key = 10 bare_key = "value" bare-key = false 1234 = {x = 0, z = 2, str = "abc"} -)toml")); - EXPECT_TRUE(TestDelete(R"toml( +)toml"); + EXPECT_EQ(DeleteNode(R"toml( key = 10 bare_key = "value" bare-key = false 1234 = {x = 0, y = 1, z = 2, str = "abc"} )toml", - "1234.x", + "1234.x"), R"toml( key = 10 bare_key = "value" bare-key = false 1234 = { y = 1, z = 2, str = "abc"} -)toml")); - EXPECT_TRUE(TestDelete(R"toml( +)toml"); + EXPECT_EQ(DeleteNode(R"toml( key = 10 bare_key = "value" bare-key = false 1234 = {x = 0, y = 1, z = 2, str = "abc"} )toml", - "1234.str", + "1234.str"), R"toml( key = 10 bare_key = "value" bare-key = false 1234 = {x = 0, y = 1, z = 2} -)toml")); - +)toml"); } -TEST(TOMLDeleteNode, DeleteInlineSubTableValues) +TEST(DeleteNode, DeleteInlineSubTableValues) { // Delete key from the inline sub-table - EXPECT_TRUE(TestDelete(R"toml( + EXPECT_EQ(DeleteNode(R"toml( key = 10 bare_key = "value" bare-key = false 1234 = {x = 0, y = 1, z = 2, str = "abc", tbl={a =1, b=2, c=3}} )toml", - "1234.tbl.b", + "1234.tbl.b"), R"toml( key = 10 bare_key = "value" bare-key = false 1234 = {x = 0, y = 1, z = 2, str = "abc", tbl={a =1, c=3}} -)toml")); - +)toml"); } -TEST(TOMLDeleteNode, DeleteInlineTables) +TEST(DeleteNode, DeleteInlineTables) { // Delete table - EXPECT_TRUE(TestDelete(R"toml( + EXPECT_EQ(DeleteNode(R"toml( key = 10 bare_key = "value" bare-key = false 1234 = {x = 0, y = 1, z = 2, str = "abc", tbl={a =1, b=2, c=3}} )toml", - "1234.tbl", + "1234.tbl"), R"toml( key = 10 bare_key = "value" bare-key = false 1234 = {x = 0, y = 1, z = 2, str = "abc"} -)toml")); - EXPECT_TRUE(TestDelete(R"toml( +)toml"); + EXPECT_EQ(DeleteNode(R"toml( key = 10 bare_key = "value" bare-key = false 1234 = {x = 0, y = 1, z = 2, str = "abc", tbl={a =1, b=2, c=3}} )toml", - "1234", + "1234"), R"toml( key = 10 bare_key = "value" bare-key = false -)toml")); +)toml"); } -TEST(TOMLDeleteNode, DeleteTableValues) +TEST(DeleteNode, DeleteTableValues) { - EXPECT_TRUE(TestDelete(R"toml( + EXPECT_EQ(DeleteNode(R"toml( [my_table] key = 10 bare_key = "value" bare-key = false )toml", - "my_table.key", + "my_table.key"), R"toml( [my_table] bare_key = "value" bare-key = false -)toml")); +)toml"); // Delete a key from the middle - EXPECT_TRUE(TestDelete(R"toml( + EXPECT_EQ(DeleteNode(R"toml( [my_table] key = 10 bare_key = "value" bare-key = false )toml", - "my_table.bare_key", + "my_table.bare_key"), R"toml( [my_table] key = 10 bare-key = false -)toml")); +)toml"); // Delete a key from the end - EXPECT_TRUE(TestDelete(R"toml( + EXPECT_EQ(DeleteNode(R"toml( [my_table] key = 10 bare_key = "value" bare-key = false )toml", - "my_table.bare-key", + "my_table.bare-key"), R"toml( [my_table] key = 10 bare_key = "value" -)toml")); +)toml"); } -TEST(TOMLDeleteNode, DeleteInlineTableValueInTable) +TEST(DeleteNode, DeleteInlineTableValueInTable) { // Delete key from the inline table in a table - EXPECT_TRUE(TestDelete(R"toml( + EXPECT_EQ(DeleteNode(R"toml( [my_table] key = 10 bare_key = "value" bare-key = false 1234 = {x = 0, y = 1, z = 2, str = "abc"} )toml", - "my_table.1234.y", + "my_table.1234.y"), R"toml( [my_table] key = 10 bare_key = "value" bare-key = false 1234 = {x = 0, z = 2, str = "abc"} -)toml")); - EXPECT_TRUE(TestDelete(R"toml( +)toml"); + EXPECT_EQ(DeleteNode(R"toml( [my_table] key = 10 bare_key = "value" bare-key = false 1234 = {x = 0, y = 1, z = 2, str = "abc"} )toml", - "my_table.1234.x", + "my_table.1234.x"), R"toml( [my_table] key = 10 bare_key = "value" bare-key = false 1234 = { y = 1, z = 2, str = "abc"} -)toml")); - EXPECT_TRUE(TestDelete(R"toml( +)toml"); + EXPECT_EQ(DeleteNode(R"toml( [my_table] key = 10 bare_key = "value" bare-key = false 1234 = {x = 0, y = 1, z = 2, str = "abc"} )toml", - "my_table.1234.str", + "my_table.1234.str"), R"toml( [my_table] key = 10 bare_key = "value" bare-key = false 1234 = {x = 0, y = 1, z = 2} -)toml")); +)toml"); } -TEST(TOMLDeleteNode, DeleteInlineSubTableValueInTable) +TEST(DeleteNode, DeleteInlineSubTableValueInTable) { // Delete key from the inline sub-table - EXPECT_TRUE(TestDelete(R"toml( + EXPECT_EQ(DeleteNode(R"toml( [my_table] key = 10 bare_key = "value" bare-key = false 1234 = {x = 0, y = 1, z = 2, str = "abc", tbl={a =1, b=2, c=3}} )toml", - "my_table.1234.tbl.b", + "my_table.1234.tbl.b"), R"toml( [my_table] key = 10 bare_key = "value" bare-key = false 1234 = {x = 0, y = 1, z = 2, str = "abc", tbl={a =1, c=3}} -)toml")); +)toml"); } -TEST(TOMLDeleteNode, DeleteInlineTableInTable) +TEST(DeleteNode, DeleteInlineTableInTable) { // Delete table - EXPECT_TRUE(TestDelete(R"toml( + EXPECT_EQ(DeleteNode(R"toml( [my_table] key = 10 bare_key = "value" bare-key = false 1234 = {x = 0, y = 1, z = 2, str = "abc", tbl={a =1, b=2, c=3}} )toml", - "my_table.1234.tbl", + "my_table.1234.tbl"), R"toml( [my_table] key = 10 bare_key = "value" bare-key = false 1234 = {x = 0, y = 1, z = 2, str = "abc"} -)toml")); - EXPECT_TRUE(TestDelete(R"toml( +)toml"); + EXPECT_EQ(DeleteNode(R"toml( [my_table] key = 10 bare_key = "value" bare-key = false 1234 = {x = 0, y = 1, z = 2, str = "abc", tbl={a =1, b=2, c=3}} )toml", - "my_table.1234", + "my_table.1234"), R"toml( [my_table] key = 10 bare_key = "value" bare-key = false -)toml")); +)toml"); } -TEST(TOMLDeleteNode, DeleteValuesInChildTable) +TEST(DeleteNode, DeleteValuesInChildTable) { // Delete key from the child-table - EXPECT_TRUE(TestDelete(R"toml( + EXPECT_EQ(DeleteNode(R"toml( [my_table] key = 10 bare_key = "value" @@ -370,7 +364,7 @@ y = 1 z = 2 str = "abc" )toml", - "my_table.1234.y", + "my_table.1234.y"), R"toml( [my_table] key = 10 @@ -380,8 +374,8 @@ bare-key = false x = 0 z = 2 str = "abc" -)toml")); - EXPECT_TRUE(TestDelete(R"toml( +)toml"); + EXPECT_EQ(DeleteNode(R"toml( [my_table] key = 10 bare_key = "value" @@ -392,7 +386,7 @@ y = 1 z = 2 str = "abc" )toml", - "my_table.1234.x", + "my_table.1234.x"), R"toml( [my_table] key = 10 @@ -402,8 +396,8 @@ bare-key = false y = 1 z = 2 str = "abc" -)toml")); - EXPECT_TRUE(TestDelete(R"toml( +)toml"); + EXPECT_EQ(DeleteNode(R"toml( [my_table] key = 10 bare_key = "value" @@ -414,7 +408,7 @@ y = 1 z = 2 str = "abc" )toml", - "my_table.1234.str", + "my_table.1234.str"), R"toml( [my_table] key = 10 @@ -424,14 +418,14 @@ bare-key = false x = 0 y = 1 z = 2 -)toml")); +)toml"); } -TEST(TOMLDeleteNode, DeleteChildTableInTable) +TEST(DeleteNode, DeleteChildTableInTable) { // Delete table - EXPECT_TRUE(TestDelete(R"toml( + EXPECT_EQ(DeleteNode(R"toml( [my_table] key = 10 bare_key = "value" @@ -446,7 +440,7 @@ a =1 b=2 c=3 )toml", - "my_table.1234.tbl", + "my_table.1234.tbl"), R"toml( [my_table] key = 10 @@ -457,8 +451,8 @@ x = 0 y = 1 z = 2 str = "abc" -)toml")); - EXPECT_TRUE(TestDelete(R"toml( +)toml"); + EXPECT_EQ(DeleteNode(R"toml( [my_table] key = 10 bare_key = "value" @@ -473,176 +467,176 @@ a =1 b=2 c=3 )toml", - "my_table.1234", + "my_table.1234"), R"toml( [my_table] key = 10 bare_key = "value" bare-key = false -)toml")); +)toml"); } -TEST(TOMLDeleteNode, DeleteArrayValues) +TEST(DeleteNode, DeleteArrayValues) { // Delete array values - EXPECT_TRUE(TestDelete(R"toml( + EXPECT_EQ(DeleteNode(R"toml( key = [10, 20, 30] bare_key = ["value1", "value2", 3030, ] bare-key = [{a = false, b = true}, # value 0 2020, # value 1 ] )toml", - "key[0]", + "key[0]"), R"toml( key = [ 20, 30] bare_key = ["value1", "value2", 3030, ] bare-key = [{a = false, b = true}, # value 0 2020, # value 1 ] -)toml")); - EXPECT_TRUE(TestDelete(R"toml( +)toml"); + EXPECT_EQ(DeleteNode(R"toml( key = [10, 20, 30] bare_key = ["value1", "value2", 3030, ] bare-key = [{a = false, b = true}, # value 0 2020, # value 1 ] )toml", - "key[1]", + "key[1]"), R"toml( key = [10, 30] bare_key = ["value1", "value2", 3030, ] bare-key = [{a = false, b = true}, # value 0 2020, # value 1 ] -)toml")); - EXPECT_TRUE(TestDelete(R"toml( +)toml"); + EXPECT_EQ(DeleteNode(R"toml( key = [10, 20, 30] bare_key = ["value1", "value2", 3030, ] bare-key = [{a = false, b = true}, # value 0 2020, # value 1 ] )toml", - "key[2]", + "key[2]"), R"toml( key = [10, 20] bare_key = ["value1", "value2", 3030, ] bare-key = [{a = false, b = true}, # value 0 2020, # value 1 ] -)toml")); +)toml"); } -TEST(TOMLDeleteNode, DeleteArrayValuesWithSucceedingComma) +TEST(DeleteNode, DeleteArrayValuesWithSucceedingComma) { // Delete array values with succeeding comma - EXPECT_TRUE(TestDelete(R"toml( + EXPECT_EQ(DeleteNode(R"toml( key = [10, 20, 30] bare_key = ["value1", "value2", 3030, ] bare-key = [{a = false, b = true}, # value 0 2020, # value 1 ] )toml", - "bare_key[0]", + "bare_key[0]"), R"toml( key = [10, 20, 30] bare_key = [ "value2", 3030, ] bare-key = [{a = false, b = true}, # value 0 2020, # value 1 ] -)toml")); - EXPECT_TRUE(TestDelete(R"toml( +)toml"); + EXPECT_EQ(DeleteNode(R"toml( key = [10, 20, 30] bare_key = ["value1", "value2", 3030, ] bare-key = [{a = false, b = true}, # value 0 2020, # value 1 ] )toml", - "bare_key[1]", + "bare_key[1]"), R"toml( key = [10, 20, 30] bare_key = ["value1", 3030, ] bare-key = [{a = false, b = true}, # value 0 2020, # value 1 ] -)toml")); - EXPECT_TRUE(TestDelete(R"toml( +)toml"); + EXPECT_EQ(DeleteNode(R"toml( key = [10, 20, 30] bare_key = ["value1", "value2", 3030, ] bare-key = [{a = false, b = true}, # value 0 2020, # value 1 ] )toml", - "bare_key[2]", + "bare_key[2]"), R"toml( key = [10, 20, 30] bare_key = ["value1", "value2", ] bare-key = [{a = false, b = true}, # value 0 2020, # value 1 ] -)toml")); +)toml"); } -TEST(TOMLDeleteNode, DeleteValuesWithComments) +TEST(DeleteNode, DeleteValuesWithComments) { // Delete array values with comments - EXPECT_TRUE(TestDelete(R"toml( + EXPECT_EQ(DeleteNode(R"toml( key = [10, 20, 30] bare_key = ["value1", "value2", 3030, ] bare-key = [{a = false, b = true}, # value 0 2020, # value 1 ] )toml", - "bare-key[0]", + "bare-key[0]"), R"toml( key = [10, 20, 30] bare_key = ["value1", "value2", 3030, ] bare-key = [ 2020, # value 1 ] -)toml")); - EXPECT_TRUE(TestDelete(R"toml( +)toml"); + EXPECT_EQ(DeleteNode(R"toml( key = [10, 20, 30] bare_key = ["value1", "value2", 3030, ] bare-key = [{a = false, b = true}, # value 0 2020, # value 1 ] )toml", - "bare-key[0].a", + "bare-key[0].a"), R"toml( key = [10, 20, 30] bare_key = ["value1", "value2", 3030, ] bare-key = [{ b = true}, # value 0 2020, # value 1 ] -)toml")); - EXPECT_TRUE(TestDelete(R"toml( +)toml"); + EXPECT_EQ(DeleteNode(R"toml( key = [10, 20, 30] bare_key = ["value1", "value2", 3030, ] bare-key = [{a = false, b = true}, # value 0 2020, # value 1 ] )toml", - "bare-key[0].b", + "bare-key[0].b"), R"toml( key = [10, 20, 30] bare_key = ["value1", "value2", 3030, ] bare-key = [{a = false}, # value 0 2020, # value 1 ] -)toml")); - EXPECT_TRUE(TestDelete(R"toml( +)toml"); + EXPECT_EQ(DeleteNode(R"toml( key = [10, 20, 30] bare_key = ["value1", "value2", 3030, ] bare-key = [{a = false, b = true}, # value 0 2020, # value 1 ] )toml", - "bare-key[1]", + "bare-key[1]"), R"toml( key = [10, 20, 30] bare_key = ["value1", "value2", 3030, ] bare-key = [{a = false, b = true}, # value 0 ] -)toml")); +)toml"); } \ No newline at end of file diff --git a/tests/unit_tests/toml_parser/generate_toml_insert_node.cpp b/tests/unit_tests/toml_parser/generate_toml_insert_node.cpp deleted file mode 100644 index b817392..0000000 --- a/tests/unit_tests/toml_parser/generate_toml_insert_node.cpp +++ /dev/null @@ -1,1068 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2025-2026 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Erik Verhoeven - initial API and implementation - ********************************************************************************/ - -#include - -#include "../../../sdv_services/core/toml_parser/parser_node_toml.h" -#include "../../../sdv_services/core/toml_parser/parser_toml.h" - -// Test TODO: -// - Inserted and straight away deleted -// - Inserted with false/deleted reference --error -// - Inserted values before (okay) and behind (error) tables -// - Inserted duplicate value -- error -// - Smart insert (comments/whitespace around) -// Insert as TOML, but only partly correct. - -TEST(TOMLInsertNode, TestInsertValuesRoot) -{ - toml_parser::CParser parser; - EXPECT_NE(parser.Root().InsertValue(sdv::toml::npos, "value_int", 10), nullptr); - std::string ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(value_int = 10)toml"); - EXPECT_NE(parser.Root().InsertValue(sdv::toml::npos, "value_str", u8"abc"), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(value_int = 10 -value_str = "abc")toml"); - EXPECT_NE(parser.Root().InsertValue(0, "value_float", 123.456), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(value_float = 123.456 -value_int = 10 -value_str = "abc")toml"); -} - -TEST(TOMLInsertNode, TestInsertTableRoot) -{ - // Insert a standard table - toml_parser::CParser parser; - EXPECT_NE(parser.Root().InsertTable(sdv::toml::npos, "table1", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard), nullptr); - std::string ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([table1])toml"); - - // Insert an inline table before - EXPECT_NE(parser.Root().InsertTable(0, "table2", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_inline), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(table2 = {} -[table1])toml"); - - // Insert an inline table behind -> this will have to be printed before the standard table - EXPECT_NE(parser.Root().InsertTable(sdv::toml::npos, "table3", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_inline), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(table2 = {} -table3 = {} -[table1])toml"); - - // Insert a standard table in front -> this will have to be printed behind the inline table - EXPECT_NE(parser.Root().InsertTable(0, "table4", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(table2 = {} -table3 = {} -[table4] -[table1])toml"); -} - -TEST(TOMLInsertNode, TestInsertArrayRoot) -{ - toml_parser::CParser parser; - EXPECT_NE(parser.Root().InsertArray(sdv::toml::npos, "value_array1"), nullptr); - std::string ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(value_array1 = [])toml"); - EXPECT_NE(parser.Root().InsertArray(sdv::toml::npos, "value_array2"), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(value_array1 = [] -value_array2 = [])toml"); - EXPECT_NE(parser.Root().InsertArray(0, "value_array3"), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(value_array3 = [] -value_array1 = [] -value_array2 = [])toml"); -} - -TEST(TOMLInsertNode, TestInsertTableArrayRoot) -{ - // Insert a standard table array - toml_parser::CParser parser; - EXPECT_NE(parser.Root().InsertTableArray(sdv::toml::npos, "table_array1", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard), nullptr); - std::string ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([[table_array1]])toml"); - - // Insert an inline table array before - EXPECT_NE(parser.Root().InsertTableArray(0, "table_array2", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_inline), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(table_array2 = [{}] -[[table_array1]])toml"); - - // Insert an inline table array behind -> this will have to be printed before the standard table array - EXPECT_NE(parser.Root().InsertTableArray(sdv::toml::npos, "table_array3", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_inline), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(table_array2 = [{}] -table_array3 = [{}] -[[table_array1]])toml"); - - // Insert a standard table array in front -> this will have to be printed behind the inline table array - EXPECT_NE(parser.Root().InsertTableArray(0, "table_array4", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(table_array2 = [{}] -table_array3 = [{}] -[[table_array4]] -[[table_array1]])toml"); - - // Add an additional table array entry for table array #4 - EXPECT_NE(parser.Root().InsertTableArray(sdv::toml::npos, "table_array4", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(table_array2 = [{}] -table_array3 = [{}] -[[table_array4]] -[[table_array1]] -[[table_array4]])toml"); -} - -TEST(TOMLInsertNode, TestInsertValueInStandardTable) -{ - // Insert a standard table - toml_parser::CParser parser; - sdv::toml::INodeCollectionInsert* pStandardTable = sdv::TInterfaceAccessPtr(parser.Root().InsertTable( - sdv::toml::npos, "standard_table", sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard)). - GetInterface(); - ASSERT_NE(pStandardTable, nullptr); - std::string ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([standard_table])toml"); - - // Insert the values into the table - EXPECT_NE(pStandardTable->InsertValue(sdv::toml::npos, "value_int", 10), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([standard_table] -value_int = 10)toml"); - EXPECT_NE(pStandardTable->InsertValue(sdv::toml::npos, "value_str", u8"abc"), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([standard_table] -value_int = 10 -value_str = "abc")toml"); - EXPECT_NE(pStandardTable->InsertValue(0, "value_float", 123.456), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([standard_table] -value_float = 123.456 -value_int = 10 -value_str = "abc")toml"); -} - -TEST(TOMLInsertNode, TestInsertTableInStandardTable) -{ - // Insert a standard table - toml_parser::CParser parser; - sdv::toml::INodeCollectionInsert* pStandardTable = sdv::TInterfaceAccessPtr(parser.Root().InsertTable( - sdv::toml::npos, "standard_table", sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard)). - GetInterface(); - ASSERT_NE(pStandardTable, nullptr); - std::string ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([standard_table])toml"); - - // Insert a standard table - EXPECT_NE(pStandardTable->InsertTable(sdv::toml::npos, "table1", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([standard_table.table1])toml"); - - // Insert an inline table before - EXPECT_NE(pStandardTable->InsertTable(0, "table2", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_inline), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([standard_table] -table2 = {} -[standard_table.table1])toml"); - - // Insert an inline table behind -> this will have to be printed before the standard table - EXPECT_NE(pStandardTable->InsertTable(sdv::toml::npos, "table3", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_inline), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([standard_table] -table2 = {} -table3 = {} -[standard_table.table1])toml"); - - // Insert a standard table in front -> this will have to be printed behind the inline table - EXPECT_NE(pStandardTable->InsertTable(0, "table4", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([standard_table] -table2 = {} -table3 = {} -[standard_table.table4] -[standard_table.table1])toml"); -} - -TEST(TOMLInsertNode, TestInsertArrayInStandardTable) -{ - // Insert a standard table - toml_parser::CParser parser; - sdv::toml::INodeCollectionInsert* pStandardTable = sdv::TInterfaceAccessPtr(parser.Root().InsertTable( - sdv::toml::npos, "standard_table", sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard)). - GetInterface(); - ASSERT_NE(pStandardTable, nullptr); - std::string ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([standard_table])toml"); - - // Insert arrays - EXPECT_NE(pStandardTable->InsertArray(sdv::toml::npos, "value_array1"), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([standard_table] -value_array1 = [])toml"); - EXPECT_NE(pStandardTable->InsertArray(sdv::toml::npos, "value_array2"), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([standard_table] -value_array1 = [] -value_array2 = [])toml"); - EXPECT_NE(pStandardTable->InsertArray(0, "value_array3"), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([standard_table] -value_array3 = [] -value_array1 = [] -value_array2 = [])toml"); -} - -TEST(TOMLInsertNode, TestInsertTableArrayInStandardTable) -{ - // Insert a standard table - toml_parser::CParser parser; - sdv::toml::INodeCollectionInsert* pStandardTable = sdv::TInterfaceAccessPtr(parser.Root().InsertTable( - sdv::toml::npos, "standard_table", sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard)). - GetInterface(); - ASSERT_NE(pStandardTable, nullptr); - std::string ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([standard_table])toml"); - - // Insert standard table array - EXPECT_NE(pStandardTable->InsertTableArray(sdv::toml::npos, "table_array1", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([[standard_table.table_array1]])toml"); - - // Insert an inline table array before - EXPECT_NE(pStandardTable->InsertTableArray(0, "table_array2", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_inline), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([standard_table] -table_array2 = [{}] -[[standard_table.table_array1]])toml"); - - // Insert an inline table array behind -> this will have to be printed before the standard table array - EXPECT_NE(pStandardTable->InsertTableArray(sdv::toml::npos, "table_array3", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_inline), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([standard_table] -table_array2 = [{}] -table_array3 = [{}] -[[standard_table.table_array1]])toml"); - - // Insert a standard table array in front -> this will have to be printed behind the inline table array - EXPECT_NE(pStandardTable->InsertTableArray(0, "table_array4", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([standard_table] -table_array2 = [{}] -table_array3 = [{}] -[[standard_table.table_array4]] -[[standard_table.table_array1]])toml"); - - // Add an additional table array entry for table array #4 - EXPECT_NE(pStandardTable->InsertTableArray(sdv::toml::npos, "table_array4", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([standard_table] -table_array2 = [{}] -table_array3 = [{}] -[[standard_table.table_array4]] -[[standard_table.table_array1]] -[[standard_table.table_array4]])toml"); -} - -TEST(TOMLInsertNode, TestInsertValueInInlineTable) -{ - // Insert an inline table - toml_parser::CParser parser; - sdv::toml::INodeCollectionInsert* pInlineTable = sdv::TInterfaceAccessPtr(parser.Root().InsertTable( - sdv::toml::npos, "inline_table", sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_inline)). - GetInterface(); - ASSERT_NE(pInlineTable, nullptr); - std::string ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_table = {})toml"); - - // Insert the values into the table - EXPECT_NE(pInlineTable->InsertValue(sdv::toml::npos, "value_int", 10), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_table = {value_int = 10})toml"); - EXPECT_NE(pInlineTable->InsertValue(sdv::toml::npos, "value_str", u8"abc"), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_table = {value_int = 10, value_str = "abc"})toml"); - EXPECT_NE(pInlineTable->InsertValue(0, "value_float", 123.456), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_table = {value_float = 123.456, value_int = 10, value_str = "abc"})toml"); -} - -TEST(TOMLInsertNode, TestInsertTableInInlineTable) -{ - // Insert an inline table - toml_parser::CParser parser; - sdv::toml::INodeCollectionInsert* pInlineTable = sdv::TInterfaceAccessPtr(parser.Root().InsertTable( - sdv::toml::npos, "inline_table", sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_inline)). - GetInterface(); - ASSERT_NE(pInlineTable, nullptr); - std::string ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_table = {})toml"); - - // Insert a standard table - EXPECT_NE(pInlineTable->InsertTable(sdv::toml::npos, "table1", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_table = {table1 = {}})toml"); - - // Insert an inline table before - EXPECT_NE(pInlineTable->InsertTable(0, "table2", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_inline), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_table = {table2 = {}, table1 = {}})toml"); - - // Insert an inline table behind -> this will have to be printed before the standard table - EXPECT_NE(pInlineTable->InsertTable(sdv::toml::npos, "table3", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_inline), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_table = {table2 = {}, table1 = {}, table3 = {}})toml"); - - // Insert a standard table in front -> this will have to be printed behind the inline table - EXPECT_NE(pInlineTable->InsertTable(0, "table4", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_table = {table4 = {}, table2 = {}, table1 = {}, table3 = {}})toml"); -} - -TEST(TOMLInsertNode, TestInsertArrayInInlineTable) -{ - // Insert an inline table - toml_parser::CParser parser; - sdv::toml::INodeCollectionInsert* pInlineTable = sdv::TInterfaceAccessPtr(parser.Root().InsertTable( - sdv::toml::npos, "inline_table", sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_inline)). - GetInterface(); - ASSERT_NE(pInlineTable, nullptr); - std::string ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_table = {})toml"); - - // Insert arrays - EXPECT_NE(pInlineTable->InsertArray(sdv::toml::npos, "value_array1"), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_table = {value_array1 = []})toml"); - EXPECT_NE(pInlineTable->InsertArray(sdv::toml::npos, "value_array2"), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_table = {value_array1 = [], value_array2 = []})toml"); - EXPECT_NE(pInlineTable->InsertArray(0, "value_array3"), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_table = {value_array3 = [], value_array1 = [], value_array2 = []})toml"); -} - -TEST(TOMLInsertNode, TestInsertTableArrayInInlineTable) -{ - // Insert an inline table - toml_parser::CParser parser; - sdv::toml::INodeCollectionInsert* pInlineTable = sdv::TInterfaceAccessPtr(parser.Root().InsertTable( - sdv::toml::npos, "inline_table", sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_inline)). - GetInterface(); - ASSERT_NE(pInlineTable, nullptr); - std::string ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_table = {})toml"); - - // Insert standard table array - EXPECT_NE(pInlineTable->InsertTableArray(sdv::toml::npos, "table_array1", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_table = {table_array1 = [{}]})toml"); - - // Insert an inline table array before - EXPECT_NE(pInlineTable->InsertTableArray(0, "table_array2", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_inline), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_table = {table_array2 = [{}], table_array1 = [{}]})toml"); - - // Insert an inline table array behind -> this will have to be printed before the standard table array - EXPECT_NE(pInlineTable->InsertTableArray(sdv::toml::npos, "table_array3", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_inline), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_table = {table_array2 = [{}], table_array1 = [{}], table_array3 = [{}]})toml"); - - // Insert a standard table array in front -> this will have to be printed behind the inline table array - EXPECT_NE(pInlineTable->InsertTableArray(0, "table_array4", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_table = {table_array4 = [{}], table_array2 = [{}], table_array1 = [{}], table_array3 = [{}]})toml"); - - // Add an additional table array entry for table array #4 - EXPECT_NE(pInlineTable->InsertTableArray(sdv::toml::npos, "table_array4", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_table = {table_array4 = [{}, {}], table_array2 = [{}], table_array1 = [{}], table_array3 = [{}]})toml"); -} - -TEST(TOMLInsertNode, TestInsertValueInArray) -{ - // Insert an array - toml_parser::CParser parser; - sdv::toml::INodeCollectionInsert* pInlineArray = sdv::TInterfaceAccessPtr(parser.Root().InsertArray( - sdv::toml::npos, "inline_array")).GetInterface(); - ASSERT_NE(pInlineArray, nullptr); - std::string ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_array = [])toml"); - - // Insert the values into the table (with or without name) - EXPECT_NE(pInlineArray->InsertValue(sdv::toml::npos, "", 10), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_array = [10])toml"); - EXPECT_NE(pInlineArray->InsertValue(sdv::toml::npos, "", u8"abc"), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_array = [10, "abc"])toml"); - EXPECT_NE(pInlineArray->InsertValue(0, "value_float", 123.456), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_array = [123.456, 10, "abc"])toml"); -} - -TEST(TOMLInsertNode, TestInsertTableInArray) -{ - // Insert an array - toml_parser::CParser parser; - sdv::toml::INodeCollectionInsert* pInlineArray = sdv::TInterfaceAccessPtr(parser.Root().InsertArray( - sdv::toml::npos, "inline_array")).GetInterface(); - ASSERT_NE(pInlineArray, nullptr); - std::string ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_array = [])toml"); - - // Insert a standard table - sdv::toml::INodeCollectionInsert* pTableInsert = sdv::TInterfaceAccessPtr( - pInlineArray->InsertTable(sdv::toml::npos, "", sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard)). - GetInterface(); - ASSERT_NE(pTableInsert, nullptr); - pTableInsert->InsertValue(0, "a", 10); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_array = [{a = 10}])toml"); - - // Insert an inline table before - pTableInsert = sdv::TInterfaceAccessPtr( - pInlineArray->InsertTable(0, "", sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_inline)). - GetInterface(); - ASSERT_NE(pTableInsert, nullptr); - pTableInsert->InsertValue(0, "b", 20); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_array = [{b = 20}, {a = 10}])toml"); - - // Insert an inline table behind - pTableInsert = sdv::TInterfaceAccessPtr( - pInlineArray->InsertTable(sdv::toml::npos, "", sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_inline)). - GetInterface(); - ASSERT_NE(pTableInsert, nullptr); - pTableInsert->InsertValue(0, "c", 30); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_array = [{b = 20}, {a = 10}, {c = 30}])toml"); - - // Insert a standard table in front - pTableInsert = sdv::TInterfaceAccessPtr( - pInlineArray->InsertTable(0, "", sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard)). - GetInterface(); - ASSERT_NE(pTableInsert, nullptr); - pTableInsert->InsertValue(0, "d", 40); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_array = [{d = 40}, {b = 20}, {a = 10}, {c = 30}])toml"); -} - -TEST(TOMLInsertNode, TestInsertArrayInArray) -{ - // Insert an array - toml_parser::CParser parser; - sdv::toml::INodeCollectionInsert* pInlineArray = sdv::TInterfaceAccessPtr(parser.Root().InsertArray( - sdv::toml::npos, "inline_array")).GetInterface(); - ASSERT_NE(pInlineArray, nullptr); - std::string ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_array = [])toml"); - - // Insert arrays - sdv::toml::INodeCollectionInsert* pArrayInsert = sdv::TInterfaceAccessPtr(pInlineArray->InsertArray(sdv::toml::npos, "")). - GetInterface(); - ASSERT_NE(pArrayInsert, nullptr); - pArrayInsert->InsertValue(0, "", 10); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_array = [[10]])toml"); - pArrayInsert = sdv::TInterfaceAccessPtr(pInlineArray->InsertArray(sdv::toml::npos, "value_array2")). - GetInterface(); - ASSERT_NE(pArrayInsert, nullptr); - pArrayInsert->InsertValue(0, "", 20); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_array = [[10], [20]])toml"); - pArrayInsert = - sdv::TInterfaceAccessPtr(pInlineArray->InsertArray(0, "value_array3")). - GetInterface(); - ASSERT_NE(pArrayInsert, nullptr); - pArrayInsert->InsertValue(0, "", 30); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_array = [[30], [10], [20]])toml"); -} - -TEST(TOMLInsertNode, TestInsertTableArrayInArray) -{ - // Insert an array - toml_parser::CParser parser; - auto* pArray = parser.Root().InsertArray(sdv::toml::npos, "inline_array"); - ASSERT_NE(pArray, nullptr); - sdv::toml::INodeCollectionInsert* pInlineArray = - sdv::TInterfaceAccessPtr(pArray).GetInterface(); - ASSERT_NE(pInlineArray, nullptr); - std::string ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_array = [])toml"); - - // Insert standard table array - sdv::toml::INodeCollectionInsert* pArrayInsert = sdv::TInterfaceAccessPtr(pInlineArray->InsertTableArray(sdv::toml::npos, - "table_array1", sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard)). - GetInterface(); - ASSERT_NE(pArrayInsert, nullptr); - pArrayInsert->InsertValue(0, "a", 10); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_array = [[{a = 10}]])toml"); - - // Insert an inline table array before - pArrayInsert = sdv::TInterfaceAccessPtr(pInlineArray->InsertTableArray(0, "table_array2", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_inline)).GetInterface(); - ASSERT_NE(pArrayInsert, nullptr); - pArrayInsert->InsertValue(0, "b", 20); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_array = [[{b = 20}], [{a = 10}]])toml"); - - // Insert an inline table array behind -> this will have to be printed before the standard table array - pArrayInsert = sdv::TInterfaceAccessPtr(pInlineArray->InsertTableArray(sdv::toml::npos, "table_array3", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_inline)).GetInterface(); - ASSERT_NE(pArrayInsert, nullptr); - pArrayInsert->InsertValue(0, "c", 30); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_array = [[{b = 20}], [{a = 10}], [{c = 30}]])toml"); - - // Insert a standard table array in front -> this will have to be printed behind the inline table array - pArrayInsert = sdv::TInterfaceAccessPtr(pInlineArray->InsertTableArray(0, "table_array4", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard)).GetInterface(); - ASSERT_NE(pArrayInsert, nullptr); - pArrayInsert->InsertValue(0, "d", 40); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_array = [[{d = 40}], [{b = 20}], [{a = 10}], [{c = 30}]])toml"); - - // Add an additional table array entry for table array #4 - sdv::toml::INodeCollection* pCollection = sdv::TInterfaceAccessPtr(pArray).GetInterface(); - ASSERT_NE(pCollection, nullptr); - sdv::toml::INodeCollectionInsert* pFirstNode = - sdv::TInterfaceAccessPtr(pCollection->GetNode(0)).GetInterface(); - ASSERT_NE(pFirstNode, nullptr); - sdv::toml::INodeCollectionInsert* pTableInsert = sdv::TInterfaceAccessPtr(pFirstNode->InsertTable(sdv::toml::npos, - "table_array4", sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard)). - GetInterface(); - ASSERT_NE(pTableInsert, nullptr); - pTableInsert->InsertValue(0, "e", 50); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(inline_array = [[{d = 40}, {e = 50}], [{b = 20}], [{a = 10}], [{c = 30}]])toml"); -} - -TEST(TOMLInsertNode, TestInsertValueInTableArray) -{ - // Insert a standard table array - toml_parser::CParser parser; - sdv::toml::INodeCollectionInsert* pTableArray = sdv::TInterfaceAccessPtr(parser.Root().InsertTableArray( - sdv::toml::npos, "table_array", sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard)). - GetInterface(); - ASSERT_NE(pTableArray, nullptr); - std::string ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([[table_array]])toml"); - - // Insert the values into the table - EXPECT_NE(pTableArray->InsertValue(sdv::toml::npos, "value_int", 10), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([[table_array]] -value_int = 10)toml"); - EXPECT_NE(pTableArray->InsertValue(sdv::toml::npos, "value_str", u8"abc"), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([[table_array]] -value_int = 10 -value_str = "abc")toml"); - EXPECT_NE(pTableArray->InsertValue(0, "value_float", 123.456), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([[table_array]] -value_float = 123.456 -value_int = 10 -value_str = "abc")toml"); -} - -TEST(TOMLInsertNode, TestInsertTableInTableArray) -{ - // Insert a standard table array - toml_parser::CParser parser; - sdv::toml::INodeCollectionInsert* pTableArray = sdv::TInterfaceAccessPtr(parser.Root().InsertTableArray( - sdv::toml::npos, "table_array", sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard)). - GetInterface(); - ASSERT_NE(pTableArray, nullptr); - std::string ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([[table_array]])toml"); - - // Insert a standard table - EXPECT_NE(pTableArray->InsertTable(sdv::toml::npos, "table1", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([[table_array]] -[table_array.table1])toml"); - - // Insert an inline table before - EXPECT_NE(pTableArray->InsertTable(0, "table2", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_inline), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([[table_array]] -table2 = {} -[table_array.table1])toml"); - - // Insert an inline table behind -> this will have to be printed before the standard table - EXPECT_NE(pTableArray->InsertTable(sdv::toml::npos, "table3", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_inline), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([[table_array]] -table2 = {} -table3 = {} -[table_array.table1])toml"); - - // Insert a standard table in front -> this will have to be printed behind the inline table - EXPECT_NE(pTableArray->InsertTable(0, "table4", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([[table_array]] -table2 = {} -table3 = {} -[table_array.table4] -[table_array.table1])toml"); -} - -TEST(TOMLInsertNode, TestInsertArrayInTableArray) -{ - // Insert a standard table array - toml_parser::CParser parser; - sdv::toml::INodeCollectionInsert* pTableArray = sdv::TInterfaceAccessPtr(parser.Root().InsertTableArray( - sdv::toml::npos, "table_array", sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard)). - GetInterface(); - ASSERT_NE(pTableArray, nullptr); - std::string ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([[table_array]])toml"); - - // Insert arrays - EXPECT_NE(pTableArray->InsertArray(sdv::toml::npos, "value_array1"), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([[table_array]] -value_array1 = [])toml"); - EXPECT_NE(pTableArray->InsertArray(sdv::toml::npos, "value_array2"), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([[table_array]] -value_array1 = [] -value_array2 = [])toml"); - EXPECT_NE(pTableArray->InsertArray(0, "value_array3"), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([[table_array]] -value_array3 = [] -value_array1 = [] -value_array2 = [])toml"); -} - -TEST(TOMLInsertNode, TestInsertTableArrayInTableArray) -{ - // Insert a standard table array - toml_parser::CParser parser; - sdv::toml::INodeCollectionInsert* pTableArray = sdv::TInterfaceAccessPtr(parser.Root().InsertTableArray( - sdv::toml::npos, "table_array", sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard)). - GetInterface(); - ASSERT_NE(pTableArray, nullptr); - std::string ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([[table_array]])toml"); - - // Insert standard table array - EXPECT_NE(pTableArray->InsertTableArray(sdv::toml::npos, "table_array1", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([[table_array]] -[[table_array.table_array1]])toml"); - - // Insert an inline table array before - EXPECT_NE(pTableArray->InsertTableArray(0, "table_array2", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_inline), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([[table_array]] -table_array2 = [{}] -[[table_array.table_array1]])toml"); - - // Insert an inline table array behind -> this will have to be printed before the standard table array - EXPECT_NE(pTableArray->InsertTableArray(sdv::toml::npos, "table_array3", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_inline), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([[table_array]] -table_array2 = [{}] -table_array3 = [{}] -[[table_array.table_array1]])toml"); - - // Insert a standard table array in front -> this will have to be printed behind the inline table array - EXPECT_NE(pTableArray->InsertTableArray(0, "table_array4", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([[table_array]] -table_array2 = [{}] -table_array3 = [{}] -[[table_array.table_array4]] -[[table_array.table_array1]])toml"); - - // Add an additional table array entry for table array #4 - EXPECT_NE(pTableArray->InsertTableArray(sdv::toml::npos, "table_array4", - sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard), nullptr); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([[table_array]] -table_array2 = [{}] -table_array3 = [{}] -[[table_array.table_array4]] -[[table_array.table_array1]] -[[table_array.table_array4]])toml"); -} - -TEST(TOMLInsertNode, TestInsertValuesRootAsTOML) -{ - toml_parser::CParser parser; - EXPECT_EQ(parser.Root().InsertTOML(sdv::toml::npos, R"toml(# This is the first value -value_int = 10)toml", true), sdv::toml::INodeCollectionInsert::EInsertResult::insert_success); - std::string ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(# This is the first value -value_int = 10)toml"); - EXPECT_EQ(parser.Root().InsertTOML(sdv::toml::npos, R"toml(value_str = "abc" # This is the second value - -# Comment in between - -# And the third value -value_float = 123.456 - -# Some -# Final -# Words :-) -)toml", true), sdv::toml::INodeCollectionInsert::EInsertResult::insert_success); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(# This is the first value -value_int = 10 -value_str = "abc" # This is the second value - -# Comment in between - -# And the third value -value_float = 123.456 - -# Some -# Final -# Words :-) -)toml"); -} - -TEST(TOMLInsertNode, TestInsertTableRootAsTOML) -{ - // Insert a standard table - toml_parser::CParser parser; - EXPECT_EQ(parser.Root().InsertTOML(sdv::toml::npos, R"toml([table1] # This is table 1 -a = 10 -b = 20.30)toml", true), sdv::toml::INodeCollectionInsert::EInsertResult::insert_success); - std::string ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([table1] # This is table 1 -a = 10 -b = 20.30)toml"); - - // Insert an inline table before - EXPECT_EQ(parser.Root().InsertTOML(0, R"toml(# This is table 2 -table2 = {c = 40, d = "50"})toml", true), sdv::toml::INodeCollectionInsert::EInsertResult::insert_success); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(# This is table 2 -table2 = {c = 40, d = "50"} -[table1] # This is table 1 -a = 10 -b = 20.30)toml"); - - // Insert an inline table behind -> this will have to be printed before the standard table - EXPECT_EQ(parser.Root().InsertTOML(sdv::toml::npos, R"toml(table3 = {e = '60'} # This is table 3 -)toml", true), sdv::toml::INodeCollectionInsert::EInsertResult::insert_success); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(# This is table 2 -table2 = {c = 40, d = "50"} -table3 = {e = '60'} # This is table 3 -[table1] # This is table 1 -a = 10 -b = 20.30)toml"); - - // Insert a standard table in front -> this will have to be printed behind the inline table - EXPECT_EQ(parser.Root().InsertTOML(0, R"toml( - -# And this is table 4 -[table4] -f = 70 -g = 80.90)toml", true), sdv::toml::INodeCollectionInsert::EInsertResult::insert_success); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(# This is table 2 -table2 = {c = 40, d = "50"} -table3 = {e = '60'} # This is table 3 - - -# And this is table 4 -[table4] -f = 70 -g = 80.90 -[table1] # This is table 1 -a = 10 -b = 20.30)toml"); -} - -TEST(TOMLInsertNode, TestInsertArrayRootAsTOML) -{ - toml_parser::CParser parser; - EXPECT_EQ(parser.Root().InsertTOML(sdv::toml::npos, R"toml(value_array1 = [10, 20, 30] # This is array 1)toml", true), - sdv::toml::INodeCollectionInsert::EInsertResult::insert_success); - std::string ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(value_array1 = [10, 20, 30] # This is array 1)toml"); - EXPECT_EQ(parser.Root().InsertTOML(0, R"toml(# This is array 2 -value_array2 = [ - 40, # This is an integer value - 50.60, # This is a float value - "70", # This is a string value - ])toml", true), sdv::toml::INodeCollectionInsert::EInsertResult::insert_success); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(# This is array 2 -value_array2 = [ - 40, # This is an integer value - 50.60, # This is a float value - "70", # This is a string value - ] -value_array1 = [10, 20, 30] # This is array 1)toml"); - EXPECT_EQ(parser.Root().InsertTOML(1, R"toml(# And finally array 3 -value_array3 = [])toml", true), sdv::toml::INodeCollectionInsert::EInsertResult::insert_success); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(# This is array 2 -value_array2 = [ - 40, # This is an integer value - 50.60, # This is a float value - "70", # This is a string value - ] -# And finally array 3 -value_array3 = [] -value_array1 = [10, 20, 30] # This is array 1)toml"); -} - -TEST(TOMLInsertNode, TestInsertTableArrayRootAsTOML) -{ - // Insert a standard table array - toml_parser::CParser parser; - EXPECT_EQ(parser.Root().InsertTOML(sdv::toml::npos, R"toml([[table_array1]] -a = 10 -b = 20)toml", true), - sdv::toml::INodeCollectionInsert::EInsertResult::insert_success); - std::string ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml([[table_array1]] -a = 10 -b = 20)toml"); - - // Insert an inline table array before - EXPECT_EQ(parser.Root().InsertTOML(0, R"toml(table_array2 = [{c = 30}, {d = 40}])toml", true), - sdv::toml::INodeCollectionInsert::EInsertResult::insert_success); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(table_array2 = [{c = 30}, {d = 40}] -[[table_array1]] -a = 10 -b = 20)toml"); - - // Insert an inline table array behind -> this will have to be printed before the standard table array - EXPECT_EQ(parser.Root().InsertTOML(sdv::toml::npos, R"toml(table_array3 = -[ - { e = 50 } -])toml", true), - sdv::toml::INodeCollectionInsert::EInsertResult::insert_success); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(table_array2 = [{c = 30}, {d = 40}] -table_array3 = -[ - { e = 50 } -] -[[table_array1]] -a = 10 -b = 20)toml"); - - // Insert a standard table array in front -> this will have to be printed behind the inline table array - EXPECT_EQ(parser.Root().InsertTOML(0, R"toml([[table_array4]] -f = 60 -g = 70)toml", true), - sdv::toml::INodeCollectionInsert::EInsertResult::insert_success); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(table_array2 = [{c = 30}, {d = 40}] -table_array3 = -[ - { e = 50 } -] -[[table_array4]] -f = 60 -g = 70 -[[table_array1]] -a = 10 -b = 20)toml"); - - // Add an additional table array entry for table array #4 (even provided as inline, must be added as standard). - EXPECT_EQ(parser.Root().InsertTOML(sdv::toml::npos, R"toml(table_array4 = [{h = 80}])toml", true), - sdv::toml::INodeCollectionInsert::EInsertResult::insert_success); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(table_array2 = [{c = 30}, {d = 40}] -table_array3 = -[ - { e = 50 } -] -[[table_array4]] -f = 60 -g = 70 -[[table_array1]] -a = 10 -b = 20 -[[table_array4]] -h = 80)toml"); - - // Add an additional table array entry for table array #4 (even provided as inline, must be added as standard). - EXPECT_EQ(parser.Root().InsertTOML(sdv::toml::npos, R"toml([[table_array3]] -i = 90 -[[table_array3]] -j = 100 -)toml", true), sdv::toml::INodeCollectionInsert::EInsertResult::insert_success); - EXPECT_EQ(parser.Root().InsertTOML(0, R"toml([[table_array3]] -k = 110)toml", true), sdv::toml::INodeCollectionInsert::EInsertResult::insert_success); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(table_array2 = [{c = 30}, {d = 40}] -table_array3 = -[{k = 110}, - { e = 50 }, -{i = 90}, {j = 100}] -[[table_array4]] -f = 60 -g = 70 -[[table_array1]] -a = 10 -b = 20 -[[table_array4]] -h = 80)toml"); - - parser.Root().AutomaticFormat(); - ssTOML = parser.GenerateTOML(); - EXPECT_EQ(ssTOML, R"toml(table_array2 = [{c = 30}, {d = 40}] -table_array3 = [{k = 110}, {e = 50}, {i = 90}, {j = 100}] -[[table_array4]] -f = 60 -g = 70 -[[table_array1]] -a = 10 -b = 20 -[[table_array4]] -h = 80)toml"); -} - -TEST(TOMLInsertNode, DISABLED_TestInsertValueInStandardTableAsTOML) -{ - //// Insert a standard table - //toml_parser::CParser parser; - //sdv::toml::INodeCollectionInsert* pStandardTable = sdv::TInterfaceAccessPtr(parser.Root().InsertTable( - // sdv::toml::npos, "standard_table", sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard)). - // GetInterface(); - //ASSERT_NE(pStandardTable, nullptr); - //std::string ssTOML = parser.GenerateTOML(); - //EXPECT_EQ(ssTOML, R"toml([standard_table])toml"); -} - -TEST(TOMLInsertNode, DISABLED_TestInsertTableInStandardTableAsTOML) -{ - //// Insert a standard table - //toml_parser::CParser parser; - //sdv::toml::INodeCollectionInsert* pStandardTable = sdv::TInterfaceAccessPtr(parser.Root().InsertTable( - // sdv::toml::npos, "standard_table", sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard)). - // GetInterface(); - //ASSERT_NE(pStandardTable, nullptr); - //std::string ssTOML = parser.GenerateTOML(); - //EXPECT_EQ(ssTOML, R"toml([standard_table])toml"); -} - -TEST(TOMLInsertNode, DISABLED_TestInsertArrayInStandardTableAsTOML) -{ - //// Insert a standard table - //toml_parser::CParser parser; - //sdv::toml::INodeCollectionInsert* pStandardTable = sdv::TInterfaceAccessPtr(parser.Root().InsertTable( - // sdv::toml::npos, "standard_table", sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard)). - // GetInterface(); - //ASSERT_NE(pStandardTable, nullptr); - //std::string ssTOML = parser.GenerateTOML(); - //EXPECT_EQ(ssTOML, R"toml([standard_table])toml"); -} - -TEST(TOMLInsertNode, DISABLED_TestInsertTableArrayInStandardTableAsTOML) -{ - //// Insert a standard table - //toml_parser::CParser parser; - //sdv::toml::INodeCollectionInsert* pStandardTable = sdv::TInterfaceAccessPtr(parser.Root().InsertTable( - // sdv::toml::npos, "standard_table", sdv::toml::INodeCollectionInsert::EInsertPreference::prefer_standard)). - // GetInterface(); - //ASSERT_NE(pStandardTable, nullptr); - //std::string ssTOML = parser.GenerateTOML(); - //EXPECT_EQ(ssTOML, R"toml([standard_table])toml"); -} - -TEST(TOMLInsertNode, DISABLED_TestInsertValueInInlineTableAsTOML) -{} - -TEST(TOMLInsertNode, DISABLED_TestInsertTableInInlineTableAsTOML) -{} - -TEST(TOMLInsertNode, DISABLED_TestInsertArrayInInlineTableAsTOML) -{} - -TEST(TOMLInsertNode, DISABLED_TestInsertTableArrayInInlineTableAsTOML) -{} - -TEST(TOMLInsertNode, DISABLED_TestInsertValueInArrayAsTOML) -{} - -TEST(TOMLInsertNode, DISABLED_TestInsertTableInArrayAsTOML) -{} - -TEST(TOMLInsertNode, DISABLED_TestInsertArrayInArrayAsTOML) -{} - -TEST(TOMLInsertNode, DISABLED_TestInsertTableArrayInArrayAsTOML) -{} - -TEST(TOMLInsertNode, DISABLED_TestInsertValueInTableArrayAsTOML) -{} - -TEST(TOMLInsertNode, DISABLED_TestInsertTableInTableArrayAsTOML) -{} - -TEST(TOMLInsertNode, DISABLED_TestInsertArrayInTableArrayAsTOML) -{} - -TEST(TOMLInsertNode, DISABLED_TestInsertTableArrayInTableArrayAsTOML) -{} - -TEST(TOMLInsertNode, DISABLED_TestInsertMixed) -{} - -TEST(TOMLInsertNode, DISABLED_TestInsertMixedWithDelete) -{} diff --git a/tests/unit_tests/toml_parser/generate_toml_switch_inline.cpp b/tests/unit_tests/toml_parser/generate_toml_switch_inline.cpp deleted file mode 100644 index 8f9344e..0000000 --- a/tests/unit_tests/toml_parser/generate_toml_switch_inline.cpp +++ /dev/null @@ -1,50 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2025-2026 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Erik Verhoeven - initial API and implementation - ********************************************************************************/ - -#include - -#include "../../../sdv_services/core/toml_parser/parser_node_toml.h" -#include "../../../sdv_services/core/toml_parser/parser_toml.h" - -// Test TODO: -// Switch to inline and vice versa - -TEST(GenerateTOML, SwitchTableToInline) -{ - toml_parser::CParser parser; - - std::string ssTOMLInput = R"code([table] -key1 = "some string" -key2 = 123)code"; - std::string ssTOMLOutput = R"code(table = {key1 = "some string", key2 = 123})code"; - - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); - - std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); - EXPECT_EQ(ssGenerated, ssTOMLInput); - - sdv::TInterfaceAccessPtr ptrTable = parser.Root().GetNodeDirect("table"); - EXPECT_TRUE(ptrTable); - sdv::toml::INodeInfo* pInfo = ptrTable.GetInterface(); - sdv::toml::INodeCollectionConvert* pConvert = ptrTable.GetInterface(); - ASSERT_NE(pInfo, nullptr); - ASSERT_NE(pConvert, nullptr); - EXPECT_FALSE(pInfo->IsInline()); - EXPECT_TRUE(pConvert->CanMakeInline()); - EXPECT_TRUE(pConvert->MakeInline()); - EXPECT_TRUE(pInfo->IsInline()); - - EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); - EXPECT_EQ(ssGenerated, ssTOMLOutput); -} diff --git a/tests/unit_tests/toml_parser/generate_toml_tests.cpp b/tests/unit_tests/toml_parser/generate_toml_tests.cpp index 55b59d3..c9c4f74 100644 --- a/tests/unit_tests/toml_parser/generate_toml_tests.cpp +++ b/tests/unit_tests/toml_parser/generate_toml_tests.cpp @@ -12,6 +12,7 @@ ********************************************************************************/ #include +#include "../../../global/localmemmgr.h" #include "../../../sdv_services/core/toml_parser/parser_toml.h" #include "../../../sdv_services/core/toml_parser/parser_node_toml.h" @@ -19,7 +20,7 @@ TEST(GenerateTOML, Comment) { toml_parser::CParser parser; - std::string ssTOML = R"code(# This is a full-line comment)code"; + std::string ssTOML = R"toml(# This is a full-line comment)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -33,9 +34,9 @@ TEST(GenerateTOML, NodeComment) { toml_parser::CParser parser; - std::string ssTOML = R"code(# This is a full-line comment + std::string ssTOML = R"toml(# This is a full-line comment key = "value" # This is a comment at the end of a line -another = "# This is not a comment")code"; +another = "# This is not a comment")toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -49,10 +50,10 @@ TEST(GenerateTOML, NodeCommentWithSpaces) { toml_parser::CParser parser; - std::string ssTOML = R"code( + std::string ssTOML = R"toml( # This is a full-line comment key = "value" # This is a comment at the end of a line -)code"; +)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -66,13 +67,13 @@ TEST(GenerateTOML, UnattachedComment) { toml_parser::CParser parser; - std::string ssTOML = R"code(# Comment not belonging to node + std::string ssTOML = R"toml(# Comment not belonging to node # This is a full-line comment key = "value" # This is a comment at the end of a line another = "# This is not a comment" -# Comment not belonging to node)code"; +# Comment not belonging to node)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -86,10 +87,10 @@ TEST(GenerateTOML, ArrayWhitespace) { toml_parser::CParser parser; - std::string ssTOML = R"code( + std::string ssTOML = R"toml( array = [ 1, 2, 3, 4, 5, 6 ] -)code"; +)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -103,7 +104,7 @@ TEST(GenerateTOML, ArrayComment) { toml_parser::CParser parser; - std::string ssTOML = R"code( + std::string ssTOML = R"toml( # Pre-array array = [ 1, # Value #1 2, # Value #2 @@ -112,7 +113,7 @@ array = [ 1, # Value #1 5, # Value #5 6, # Value #6 ] # Post-array -)code"; +)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -126,7 +127,7 @@ TEST(GenerateTOML, ArrayCommentWithSpace) { toml_parser::CParser parser; - std::string ssTOML = R"code( + std::string ssTOML = R"toml( # unattached comment @@ -159,7 +160,7 @@ array = [ 1, # Value #1 # unattached comment -)code"; +)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -174,9 +175,9 @@ TEST(GenerateTOML, InlineTableWhitespace) toml_parser::CParser parser; // Note: line-breaks within an inline table are not allowed. - std::string ssTOML = R"code( + std::string ssTOML = R"toml( table = { a = 1, b = 2, d = 3, e = 4, f = 5, g = 6 } -)code"; +)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -191,10 +192,10 @@ TEST(GenerateTOML, InlineTableComment) toml_parser::CParser parser; // Note: line-breaks within an inline table are not allowed. - std::string ssTOML = R"code( + std::string ssTOML = R"toml( # Pre-table table = { a = 1, b = 2, d = 3, e = 4, f = 5, g = 6 } # Post-table -)code"; +)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -208,10 +209,10 @@ TEST(GenerateTOML, Keys) { toml_parser::CParser parser; - std::string ssTOML = R"code(key = "value" + std::string ssTOML = R"toml(key = "value" bare_key = "value" bare-key = "value" -1234 = "value")code"; +1234 = "value")toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -225,11 +226,11 @@ TEST(GenerateTOML, QuotedKeys) { toml_parser::CParser parser; - std::string ssTOML = u8R"code("127.0.0.1" = "value" + std::string ssTOML = u8R"toml("127.0.0.1" = "value" "character encoding" = "value" "ʎǝʞ" = "value" 'key2' = "value" -'quoted "value"' = "value")code"; +'quoted "value"' = "value")toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -241,8 +242,8 @@ TEST(GenerateTOML, QuotedKeys) TEST(GenerateTOML, BlankKeys) { - std::string ssTOML1 = R"code("" = "blank" # VALID but discouraged)code"; - std::string ssTOML2 = R"code('' = 'blank' # VALID but discouraged)code"; + std::string ssTOML1 = R"toml("" = "blank" # VALID but discouraged)toml"; + std::string ssTOML2 = R"toml('' = 'blank' # VALID but discouraged)toml"; toml_parser::CParser parser1, parser2; EXPECT_NO_THROW(parser1.Process(ssTOML1)); @@ -259,10 +260,10 @@ TEST(GenerateTOML, DottedKeys) { toml_parser::CParser parser; - std::string ssTOML = R"code(name = "Orange" + std::string ssTOML = R"toml(name = "Orange" physical.color = "orange" physical.shape = "round" -site."google.com" = true)code"; +site."google.com" = true)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -276,9 +277,9 @@ TEST(GenerateTOML, WhitespaceKeys) { toml_parser::CParser parser; - std::string ssTOML = R"code(fruit.name = "banana" # this is best practice + std::string ssTOML = R"toml(fruit.name = "banana" # this is best practice fruit. color = "yellow" # same as fruit.color -fruit . flavor = "banana" # same as fruit.flavor)code"; +fruit . flavor = "banana" # same as fruit.flavor)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -292,14 +293,14 @@ TEST(GenerateTOML, OutOfOrderKeys) { toml_parser::CParser parser; - std::string ssTOML = R"code(apple.type = "fruit" + std::string ssTOML = R"toml(apple.type = "fruit" orange.type = "fruit" apple.skin = "thin" orange.skin = "thick" apple.color = "red" -orange.color = "orange")code"; +orange.color = "orange")toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -313,7 +314,7 @@ TEST(GenerateTOML, FloatLookingAlikeKeys) { toml_parser::CParser parser; - std::string ssTOML = R"code(3.14159 = "pi")code"; + std::string ssTOML = R"toml(3.14159 = "pi")toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -327,7 +328,7 @@ TEST(GenerateTOML, BasicStrings) { toml_parser::CParser parser; - std::string ssTOML = R"code(str = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF.")code"; + std::string ssTOML = R"toml(str = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF.")toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -341,9 +342,9 @@ TEST(GenerateTOML, MultiLineStrings) { toml_parser::CParser parser; - std::string ssTOML = R"code(str1 = """ + std::string ssTOML = R"toml(str1 = """ Roses are red -Violets are blue""")code"; +Violets are blue""")toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -357,7 +358,7 @@ TEST(GenerateTOML, LongMultiLineStrings) { toml_parser::CParser parser; - std::string ssTOML = R"code(str1 = "The quick brown fox jumps over the lazy dog." + std::string ssTOML = R"toml(str1 = "The quick brown fox jumps over the lazy dog." str2 = """ The quick brown \ @@ -370,7 +371,7 @@ str3 = """\ The quick brown \ fox jumps over \ the lazy dog.\ - """)code"; + """)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -384,13 +385,13 @@ TEST(GenerateTOML, QuotingStrings) { toml_parser::CParser parser; - std::string ssTOML = R"code(str4 = """Here are two quotation marks: "". Simple enough.""" + std::string ssTOML = R"toml(str4 = """Here are two quotation marks: "". Simple enough.""" # str5 = """Here are three quotation marks: """.""" # INVALID str5 = """Here are three quotation marks: ""\".""" str6 = """Here are fifteen quotation marks: ""\"""\"""\"""\"""\".""" # "This," she said, "is just a pointless statement." -str7 = """"This," she said, "is just a pointless statement."""")code"; +str7 = """"This," she said, "is just a pointless statement."""")toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -404,10 +405,10 @@ TEST(GenerateTOML, LiteralStrings) { toml_parser::CParser parser; - std::string ssTOML = R"code(winpath = 'C:\Users\nodejs\templates' + std::string ssTOML = R"toml(winpath = 'C:\Users\nodejs\templates' winpath2 = '\\ServerX\admin$\system32\' quoted = 'Tom "Dubs" Preston-Werner' -regex = '<\i\c*\s*>')code"; +regex = '<\i\c*\s*>')toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -421,13 +422,13 @@ TEST(GenerateTOML, MultiLineLiteralStrings) { toml_parser::CParser parser; - std::string ssTOML = R"code(regex2 = '''I [dw]on't need \d{2} apples''' + std::string ssTOML = R"toml(regex2 = '''I [dw]on't need \d{2} apples''' lines = ''' The first newline is trimmed in raw strings. All other whitespace is preserved. -''')code"; +''')toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -441,13 +442,13 @@ TEST(GenerateTOML, QuotedLiteralStrings) { toml_parser::CParser parser; - std::string ssTOML = R"code(quot15 = '''Here are fifteen quotation marks: """""""""""""""''' + std::string ssTOML = R"toml(quot15 = '''Here are fifteen quotation marks: """""""""""""""''' # apos15 = '''Here are fifteen apostrophes: '''''''''''''''''' # INVALID apos15 = "Here are fifteen apostrophes: '''''''''''''''" # 'That,' she said, 'is still pointless.' -str = ''''That,' she said, 'is still pointless.'''')code"; +str = ''''That,' she said, 'is still pointless.'''')toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -461,10 +462,10 @@ TEST(GenerateTOML, Integers) { toml_parser::CParser parser; - std::string ssTOML = R"code(int1 = +99 + std::string ssTOML = R"toml(int1 = +99 int2 = 42 int3 = 0 -int4 = -17)code"; +int4 = -17)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -478,10 +479,10 @@ TEST(GenerateTOML, ReadibleIntegers) { toml_parser::CParser parser; - std::string ssTOML = R"code(int5 = 1_000 + std::string ssTOML = R"toml(int5 = 1_000 int6 = 5_349_221 int7 = 53_49_221 # Indian number system grouping -int8 = 1_2_3_4_5 # VALID but discouraged)code"; +int8 = 1_2_3_4_5 # VALID but discouraged)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -495,7 +496,7 @@ TEST(GenerateTOML, OtherBaseIntegers) { toml_parser::CParser parser; - std::string ssTOML = R"code(# hexadecimal with prefix `0x` + std::string ssTOML = R"toml(# hexadecimal with prefix `0x` hex1 = 0xDEADBEEF hex2 = 0xdeadbeef hex3 = 0xdead_beef @@ -505,7 +506,7 @@ oct1 = 0o01234567 oct2 = 0o755 # useful for Unix file permissions # binary with prefix `0b` -bin1 = 0b11010110)code"; +bin1 = 0b11010110)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -519,7 +520,7 @@ TEST(GenerateTOML, FloatingPoints) { toml_parser::CParser parser; - std::string ssTOML = R"code(# fractional + std::string ssTOML = R"toml(# fractional flt1 = +1.0 flt2 = 3.1415 flt3 = -0.01 @@ -530,7 +531,7 @@ flt5 = 1e06 flt6 = -2E-2 # both -flt7 = 6.626e-34)code"; +flt7 = 6.626e-34)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -544,7 +545,7 @@ TEST(GenerateTOML, ReadibleFloatingPoints) { toml_parser::CParser parser; - std::string ssTOML = R"code(flt8 = 224_617.445_991_228)code"; + std::string ssTOML = R"toml(flt8 = 224_617.445_991_228)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -558,7 +559,7 @@ TEST(GenerateTOML, SpecialFloatingPoints) { toml_parser::CParser parser; - std::string ssTOML = R"code(# infinity + std::string ssTOML = R"toml(# infinity sf1 = inf # positive infinity sf2 = +inf # positive infinity sf3 = -inf # negative infinity @@ -566,7 +567,7 @@ sf3 = -inf # negative infinity # not a number sf4 = nan # actual sNaN/qNaN encoding is implementation-specific sf5 = +nan # same as `nan` -sf6 = -nan # valid, actual encoding is implementation-specific)code"; +sf6 = -nan # valid, actual encoding is implementation-specific)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -580,8 +581,8 @@ TEST(GenerateTOML, Booleans) { toml_parser::CParser parser; - std::string ssTOML = R"code(bool1 = true -bool2 = false)code"; + std::string ssTOML = R"toml(bool1 = true +bool2 = false)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -595,9 +596,9 @@ TEST(GenerateTOML, DISABLED_OffsetDateTimes) { toml_parser::CParser parser; - std::string ssTOML = R"code(odt1 = 1979-05-27T07:32:00Z + std::string ssTOML = R"toml(odt1 = 1979-05-27T07:32:00Z odt2 = 1979-05-27T00:32:00-07:00 -odt3 = 1979-05-27T00:32:00.999999-07:00)code"; +odt3 = 1979-05-27T00:32:00.999999-07:00)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -611,7 +612,7 @@ TEST(GenerateTOML, DISABLED_ReadibleOffsetDateTimes) { toml_parser::CParser parser; - std::string ssTOML = R"code(odt4 = 1979-05-27 07:32:00Z)code"; + std::string ssTOML = R"toml(odt4 = 1979-05-27 07:32:00Z)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -625,8 +626,8 @@ TEST(GenerateTOML, DISABLED_LocalDateTimes) { toml_parser::CParser parser; - std::string ssTOML = R"code(ldt1 = 1979-05-27T07:32:00 -ldt2 = 1979-05-27T00:32:00.999999)code"; + std::string ssTOML = R"toml(ldt1 = 1979-05-27T07:32:00 +ldt2 = 1979-05-27T00:32:00.999999)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -640,7 +641,7 @@ TEST(GenerateTOML, DISABLED_LocalDates) { toml_parser::CParser parser; - std::string ssTOML = R"code(ld1 = 1979-05-27)code"; + std::string ssTOML = R"toml(ld1 = 1979-05-27)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -654,8 +655,8 @@ TEST(GenerateTOML, DISABLED_LocalTimes) { toml_parser::CParser parser; - std::string ssTOML = R"code(lt1 = 07:32:00 -lt2 = 00:32:00.999999)code"; + std::string ssTOML = R"toml(lt1 = 07:32:00 +lt2 = 00:32:00.999999)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -669,7 +670,7 @@ TEST(GenerateTOML, Arrays) { toml_parser::CParser parser; - std::string ssTOML = R"code(integers = [ 1, 2, 3 ] + std::string ssTOML = R"toml(integers = [ 1, 2, 3 ] colors = [ "red", "yellow", "green" ] nested_arrays_of_ints = [ [ 1, 2 ], [3, 4, 5] ] nested_mixed_array = [ [ 1, 2 ], ["a", "b", "c"] ] @@ -681,7 +682,7 @@ contributors = [ "Foo Bar ", { name = "Baz Qux", email = "bazqux@example.com", url = "https://example.com/bazqux" } - ])code"; + ])toml"; parser.Process(ssTOML); EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -696,14 +697,14 @@ TEST(GenerateTOML, MultiLineArrays) { toml_parser::CParser parser; - std::string ssTOML = R"code(integers2 = [ + std::string ssTOML = R"toml(integers2 = [ 1, 2, 3 ] integers3 = [ 1, 2, # this is ok -])code"; +])toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -717,7 +718,7 @@ TEST(GenerateTOML, Tables) { toml_parser::CParser parser; - std::string ssTOML = R"code([table] + std::string ssTOML = R"toml([table] [table-1] key1 = "some string" @@ -725,7 +726,7 @@ key2 = 123 [table-2] key1 = "another string" -key2 = 456)code"; +key2 = 456)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -739,8 +740,8 @@ TEST(GenerateTOML, QuotedKeyTables) { toml_parser::CParser parser; - std::string ssTOML = R"code([dog."tater.man"] -type.name = "pug")code"; + std::string ssTOML = R"toml([dog."tater.man"] +type.name = "pug")toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -754,14 +755,14 @@ TEST(GenerateTOML, WhitespaceKeyTables) { toml_parser::CParser parser; - std::string ssTOML = u8R"code([a.b.c] # this is best practice + std::string ssTOML = u8R"toml([a.b.c] # this is best practice x = 1 [ d.e.f ] # same as [d.e.f] y = 1 [ g . h . i ] # same as [g.h.i] z = 1 [ j . "ʞ" . 'l' ] # same as [j."ʞ".'l'] -a = 1)code"; +a = 1)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -775,13 +776,13 @@ TEST(GenerateTOML, MixedOrderTables) { toml_parser::CParser parser; - std::string ssTOML = R"code(# VALID BUT DISCOURAGED + std::string ssTOML = R"toml(# VALID BUT DISCOURAGED [fruit.apple] a = 1 [animal] b = 2 [fruit.orange] -aa = 11)code"; +aa = 11)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -795,14 +796,14 @@ TEST(GenerateTOML, MixedValueAndTables) { toml_parser::CParser parser; - std::string ssTOML = R"code(# Top-level table begins. + std::string ssTOML = R"toml(# Top-level table begins. name = "Fido" breed = "pug" # Top-level table ends. [owner] name = "Regina Dogman" -member_since = 1999)code"; +member_since = 1999)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -816,10 +817,10 @@ TEST(GenerateTOML, AutomaticTables) { toml_parser::CParser parser; - std::string ssTOML = R"code(fruit.apple.color = "red" -fruit.apple.taste.sweet = true)code"; - std::string ssTOMLOutput2 = R"code(apple.color = "red" -apple.taste.sweet = true)code"; + std::string ssTOML = R"toml(fruit.apple.color = "red" +fruit.apple.taste.sweet = true)toml"; + std::string ssTOMLOutput2 = R"toml(apple.color = "red" +apple.taste.sweet = true)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -835,7 +836,7 @@ TEST(GenerateTOML, MixedAutomaticTables) { toml_parser::CParser parser; - std::string ssTOML = R"code([fruit] + std::string ssTOML = R"toml([fruit] apple.color = "red" apple.taste.sweet = true @@ -843,7 +844,7 @@ apple.taste.sweet = true # [fruit.apple.taste] # INVALID [fruit.apple.texture] # you can add sub-tables -smooth = true)code"; +smooth = true)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -857,9 +858,9 @@ TEST(GenerateTOML, InlineTables) { toml_parser::CParser parser; - std::string ssTOML = R"code(name = { first = "Tom", last = "Preston-Werner" } + std::string ssTOML = R"toml(name = { first = "Tom", last = "Preston-Werner" } point = { x = 1, y = 2 } -animal = { type.name = "pug" })code"; +animal = { type.name = "pug" })toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -873,9 +874,9 @@ TEST(GenerateTOML, EmbeddedInlineTables) { toml_parser::CParser parser; - std::string ssTOML = R"code(test=[{ first = "Tom", last = "Preston-Werner" }, + std::string ssTOML = R"toml(test=[{ first = "Tom", last = "Preston-Werner" }, { x = 1, y = 2 }, -{ type.name = "pug" }])code"; +{ type.name = "pug" }])toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -889,7 +890,7 @@ TEST(GenerateTOML, TableArrays) { toml_parser::CParser parser; - std::string ssTOML = R"code([[products]] + std::string ssTOML = R"toml([[products]] name = "Hammer" sku = 738594937 @@ -899,7 +900,7 @@ sku = 738594937 name = "Nail" sku = 284758393 -color = "gray")code"; +color = "gray")toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -913,7 +914,7 @@ TEST(GenerateTOML, MixedTableAndTableArrays) { toml_parser::CParser parser; - std::string ssTOML = R"code([[fruits]] + std::string ssTOML = R"toml([[fruits]] name = "apple" [fruits.physical] # subtable @@ -931,29 +932,29 @@ name = "granny smith" name = "banana" [[fruits.varieties]] -name = "plantain")code"; - std::string ssTOMLFruits1Physical = R"code([physical] # subtable +name = "plantain")toml"; + std::string ssTOMLFruits1Physical = R"toml([physical] # subtable color = "red" shape = "round" -)code"; - std::string ssTOMLFruits1Varieties = R"code([[varieties]] # nested array of tables +)toml"; + std::string ssTOMLFruits1Varieties = R"toml([[varieties]] # nested array of tables name = "red delicious" [[varieties]] name = "granny smith" -)code"; - std::string ssTOMLFruits1Variety1 = R"code([variety] # nested array of tables +)toml"; + std::string ssTOMLFruits1Variety1 = R"toml([variety] # nested array of tables name = "red delicious" -)code"; - std::string ssTOMLFruits1Variety2 = R"code([variety] +)toml"; + std::string ssTOMLFruits1Variety2 = R"toml([variety] name = "granny smith" -)code"; - std::string ssTOMLFruits2Variety1 = R"code([variety] -name = "plantain")code"; +)toml"; + std::string ssTOMLFruits2Variety1 = R"toml([variety] +name = "plantain")toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -998,9 +999,9 @@ TEST(GenerateTOML, InlineTableArrays) { toml_parser::CParser parser; - std::string ssTOML = R"code(points = [ { x = 1, y = 2, z = 3 }, + std::string ssTOML = R"toml(points = [ { x = 1, y = 2, z = 3 }, { x = 7, y = 8, z = 9 }, - { x = 2, y = 4, z = 8 } ])code"; + { x = 2, y = 4, z = 8 } ])toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); diff --git a/tests/unit_tests/toml_parser/generate_toml_with_transfer.cpp b/tests/unit_tests/toml_parser/generate_toml_with_transfer.cpp index 74ccb0f..6901b36 100644 --- a/tests/unit_tests/toml_parser/generate_toml_with_transfer.cpp +++ b/tests/unit_tests/toml_parser/generate_toml_with_transfer.cpp @@ -12,6 +12,7 @@ ********************************************************************************/ #include +#include "../../../global/localmemmgr.h" #include "../../../sdv_services/core/toml_parser/parser_toml.h" #include "../../../sdv_services/core/toml_parser/parser_node_toml.h" @@ -19,13 +20,13 @@ TEST(GenerateTOML, TransferNodeComment) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(# This is a full-line comment + std::string ssTOMLInput = R"toml(# This is a full-line comment key = "value" # This is a comment at the end of a line -another = "# This is not a comment")code"; - std::string ssTOMLOutput = R"code([tree.branch] +another = "# This is not a comment")toml"; + std::string ssTOMLOutput = R"toml([tree.branch] # This is a full-line comment key = "value" # This is a comment at the end of a line -another = "# This is not a comment")code"; +another = "# This is not a comment")toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -39,15 +40,15 @@ TEST(GenerateTOML, TransferNodeCommentWithSpaces) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code( + std::string ssTOMLInput = R"toml( # This is a full-line comment key = "value" # This is a comment at the end of a line -)code"; - std::string ssTOMLOutput = R"code([tree.branch] +)toml"; + std::string ssTOMLOutput = R"toml([tree.branch] # This is a full-line comment key = "value" # This is a comment at the end of a line -)code"; +)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -61,21 +62,21 @@ TEST(GenerateTOML, TransferUnattachedComment) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(# Comment not belonging to node + std::string ssTOMLInput = R"toml(# Comment not belonging to node # This is a full-line comment key = "value" # This is a comment at the end of a line another = "# This is not a comment" -# Comment not belonging to node)code"; - std::string ssTOMLOutput = R"code([tree.branch] +# Comment not belonging to node)toml"; + std::string ssTOMLOutput = R"toml([tree.branch] # Comment not belonging to node # This is a full-line comment key = "value" # This is a comment at the end of a line another = "# This is not a comment" -# Comment not belonging to node)code"; +# Comment not belonging to node)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -89,15 +90,15 @@ TEST(GenerateTOML, TransferArrayWhitespace) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code( + std::string ssTOMLInput = R"toml( array = [ 1, 2, 3, 4, 5, 6 ] -)code"; - std::string ssTOMLOutput = R"code([tree.branch] +)toml"; + std::string ssTOMLOutput = R"toml([tree.branch] array = [ 1, 2, 3, 4, 5, 6 ] -)code"; +)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -111,7 +112,7 @@ TEST(GenerateTOML, TransferArrayComment) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code( + std::string ssTOMLInput = R"toml( # Pre-array array = [ 1, # Value #1 2, # Value #2 @@ -120,8 +121,8 @@ array = [ 1, # Value #1 5, # Value #5 6, # Value #6 ] # Post-array -)code"; - std::string ssTOMLOutput = R"code([tree.branch] +)toml"; + std::string ssTOMLOutput = R"toml([tree.branch] # Pre-array array = [ 1, # Value #1 @@ -131,7 +132,7 @@ array = [ 1, # Value #1 5, # Value #5 6, # Value #6 ] # Post-array -)code"; +)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -145,7 +146,7 @@ TEST(GenerateTOML, TransferArrayCommentWithSpace) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code( + std::string ssTOMLInput = R"toml( # unattached comment @@ -178,8 +179,8 @@ array = [ 1, # Value #1 # unattached comment -)code"; - std::string ssTOMLOutput = R"code([tree.branch] +)toml"; + std::string ssTOMLOutput = R"toml([tree.branch] # unattached comment @@ -213,7 +214,7 @@ array = [ 1, # Value #1 # unattached comment -)code"; +)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -228,13 +229,13 @@ TEST(GenerateTOML, TransferInlineTableWhitespace) toml_parser::CParser parser; // NOTE: Line-breaks within inline tables are not allowed. - std::string ssTOMLInput = R"code( + std::string ssTOMLInput = R"toml( table = { a = 1, b = 2, d = 3, e = 4, f = 5, g = 6 } -)code"; - std::string ssTOMLOutput = R"code([tree.branch] +)toml"; + std::string ssTOMLOutput = R"toml([tree.branch] table = { a = 1, b = 2, d = 3, e = 4, f = 5, g = 6 } -)code"; +)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -249,15 +250,15 @@ TEST(GenerateTOML, TransferInlineTableComment) toml_parser::CParser parser; // NOTE: Line-breaks within inline tables are not allowed. - std::string ssTOMLInput = R"code( + std::string ssTOMLInput = R"toml( # Pre-table table = { a = 1, b = 2, d = 3, e = 4, f = 5, g = 6 } # Post-table -)code"; - std::string ssTOMLOutput = R"code([tree.branch] +)toml"; + std::string ssTOMLOutput = R"toml([tree.branch] # Pre-table table = { a = 1, b = 2, d = 3, e = 4, f = 5, g = 6 } # Post-table -)code"; +)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -271,15 +272,15 @@ TEST(GenerateTOML, TransferKeys) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(key = "value" + std::string ssTOMLInput = R"toml(key = "value" bare_key = "value" bare-key = "value" -1234 = "value")code"; - std::string ssTOMLOutput = R"code([tree.branch] +1234 = "value")toml"; + std::string ssTOMLOutput = R"toml([tree.branch] key = "value" bare_key = "value" bare-key = "value" -1234 = "value")code"; +1234 = "value")toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -293,17 +294,17 @@ TEST(GenerateTOML, TransferQuotedKeys) { toml_parser::CParser parser; - std::string ssTOMLInput = u8R"code("127.0.0.1" = "value" + std::string ssTOMLInput = u8R"toml("127.0.0.1" = "value" "character encoding" = "value" "ʎǝʞ" = "value" 'key2' = "value" -'quoted "value"' = "value")code"; - std::string ssTOMLOutput = u8R"code([tree.branch] +'quoted "value"' = "value")toml"; + std::string ssTOMLOutput = u8R"toml([tree.branch] "127.0.0.1" = "value" "character encoding" = "value" "ʎǝʞ" = "value" 'key2' = "value" -'quoted "value"' = "value")code"; +'quoted "value"' = "value")toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -315,12 +316,12 @@ TEST(GenerateTOML, TransferQuotedKeys) TEST(GenerateTOML, TransferBlankKeys) { - std::string ssTOMLInput1 = R"code("" = "blank" # VALID but discouraged)code"; - std::string ssTOMLInput2 = R"code('' = 'blank' # VALID but discouraged)code"; - std::string ssTOMLOutput1 = R"code([tree.branch] -"" = "blank" # VALID but discouraged)code"; - std::string ssTOMLOutput2 = R"code([tree.branch] -'' = 'blank' # VALID but discouraged)code"; + std::string ssTOMLInput1 = R"toml("" = "blank" # VALID but discouraged)toml"; + std::string ssTOMLInput2 = R"toml('' = 'blank' # VALID but discouraged)toml"; + std::string ssTOMLOutput1 = R"toml([tree.branch] +"" = "blank" # VALID but discouraged)toml"; + std::string ssTOMLOutput2 = R"toml([tree.branch] +'' = 'blank' # VALID but discouraged)toml"; toml_parser::CParser parser1, parser2; EXPECT_NO_THROW(parser1.Process(ssTOMLInput1)); @@ -338,15 +339,15 @@ TEST(GenerateTOML, TransferDottedKeys) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(name = "Orange" + std::string ssTOMLInput = R"toml(name = "Orange" physical.color = "orange" physical.shape = "round" -site."google.com" = true)code"; - std::string ssTOMLOutput = R"code([tree.branch] +site."google.com" = true)toml"; + std::string ssTOMLOutput = R"toml([tree.branch] name = "Orange" physical.color = "orange" physical.shape = "round" -site."google.com" = true)code"; +site."google.com" = true)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -360,13 +361,13 @@ TEST(GenerateTOML, TransferWhitespaceKeys) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(fruit.name = "banana" # this is best practice + std::string ssTOMLInput = R"toml(fruit.name = "banana" # this is best practice fruit. color = "yellow" # same as fruit.color -fruit . flavor = "banana" # same as fruit.flavor)code"; - std::string ssTOMLOutput = R"code([tree.branch] +fruit . flavor = "banana" # same as fruit.flavor)toml"; + std::string ssTOMLOutput = R"toml([tree.branch] fruit.name = "banana" # this is best practice fruit. color = "yellow" # same as fruit.color -fruit . flavor = "banana" # same as fruit.flavor)code"; +fruit . flavor = "banana" # same as fruit.flavor)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -380,15 +381,15 @@ TEST(GenerateTOML, TransferOutOfOrderKeys) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(apple.type = "fruit" + std::string ssTOMLInput = R"toml(apple.type = "fruit" orange.type = "fruit" apple.skin = "thin" orange.skin = "thick" apple.color = "red" -orange.color = "orange")code"; - std::string ssTOMLOutput = R"code([tree.branch] +orange.color = "orange")toml"; + std::string ssTOMLOutput = R"toml([tree.branch] apple.type = "fruit" orange.type = "fruit" @@ -396,7 +397,7 @@ apple.skin = "thin" orange.skin = "thick" apple.color = "red" -orange.color = "orange")code"; +orange.color = "orange")toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -410,9 +411,9 @@ TEST(GenerateTOML, TransferFloatLookingAlikeKeys) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(3.14159 = "pi")code"; - std::string ssTOMLOutput = R"code([tree.branch] -3.14159 = "pi")code"; + std::string ssTOMLInput = R"toml(3.14159 = "pi")toml"; + std::string ssTOMLOutput = R"toml([tree.branch] +3.14159 = "pi")toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -426,9 +427,9 @@ TEST(GenerateTOML, TransferBasicStrings) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(str = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF.")code"; - std::string ssTOMLOutput = R"code([tree.branch] -str = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF.")code"; + std::string ssTOMLInput = R"toml(str = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF.")toml"; + std::string ssTOMLOutput = R"toml([tree.branch] +str = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF.")toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -442,13 +443,13 @@ TEST(GenerateTOML, TransferMultiLineStrings) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(str1 = """ + std::string ssTOMLInput = R"toml(str1 = """ Roses are red -Violets are blue""")code"; - std::string ssTOMLOutput = R"code([tree.branch] +Violets are blue""")toml"; + std::string ssTOMLOutput = R"toml([tree.branch] str1 = """ Roses are red -Violets are blue""")code"; +Violets are blue""")toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -462,7 +463,7 @@ TEST(GenerateTOML, TransferLongMultiLineStrings) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(str1 = "The quick brown fox jumps over the lazy dog." + std::string ssTOMLInput = R"toml(str1 = "The quick brown fox jumps over the lazy dog." str2 = """ The quick brown \ @@ -475,8 +476,8 @@ str3 = """\ The quick brown \ fox jumps over \ the lazy dog.\ - """)code"; - std::string ssTOMLOutput = R"code([tree.branch] + """)toml"; + std::string ssTOMLOutput = R"toml([tree.branch] str1 = "The quick brown fox jumps over the lazy dog." str2 = """ @@ -490,7 +491,7 @@ str3 = """\ The quick brown \ fox jumps over \ the lazy dog.\ - """)code"; + """)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -504,21 +505,21 @@ TEST(GenerateTOML, TransferQuotingStrings) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(str4 = """Here are two quotation marks: "". Simple enough.""" + std::string ssTOMLInput = R"toml(str4 = """Here are two quotation marks: "". Simple enough.""" # str5 = """Here are three quotation marks: """.""" # INVALID str5 = """Here are three quotation marks: ""\".""" str6 = """Here are fifteen quotation marks: ""\"""\"""\"""\"""\".""" # "This," she said, "is just a pointless statement." -str7 = """"This," she said, "is just a pointless statement."""")code"; - std::string ssTOMLOutput = R"code([tree.branch] +str7 = """"This," she said, "is just a pointless statement."""")toml"; + std::string ssTOMLOutput = R"toml([tree.branch] str4 = """Here are two quotation marks: "". Simple enough.""" # str5 = """Here are three quotation marks: """.""" # INVALID str5 = """Here are three quotation marks: ""\".""" str6 = """Here are fifteen quotation marks: ""\"""\"""\"""\"""\".""" # "This," she said, "is just a pointless statement." -str7 = """"This," she said, "is just a pointless statement."""")code"; +str7 = """"This," she said, "is just a pointless statement."""")toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -532,15 +533,15 @@ TEST(GenerateTOML, TransferLiteralStrings) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(winpath = 'C:\Users\nodejs\templates' + std::string ssTOMLInput = R"toml(winpath = 'C:\Users\nodejs\templates' winpath2 = '\\ServerX\admin$\system32\' quoted = 'Tom "Dubs" Preston-Werner' -regex = '<\i\c*\s*>')code"; - std::string ssTOMLOutput = R"code([tree.branch] +regex = '<\i\c*\s*>')toml"; + std::string ssTOMLOutput = R"toml([tree.branch] winpath = 'C:\Users\nodejs\templates' winpath2 = '\\ServerX\admin$\system32\' quoted = 'Tom "Dubs" Preston-Werner' -regex = '<\i\c*\s*>')code"; +regex = '<\i\c*\s*>')toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -554,21 +555,21 @@ TEST(GenerateTOML, TransferMultiLineLiteralStrings) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(regex2 = '''I [dw]on't need \d{2} apples''' + std::string ssTOMLInput = R"toml(regex2 = '''I [dw]on't need \d{2} apples''' lines = ''' The first newline is trimmed in raw strings. All other whitespace is preserved. -''')code"; - std::string ssTOMLOutput = R"code([tree.branch] +''')toml"; + std::string ssTOMLOutput = R"toml([tree.branch] regex2 = '''I [dw]on't need \d{2} apples''' lines = ''' The first newline is trimmed in raw strings. All other whitespace is preserved. -''')code"; +''')toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -582,21 +583,21 @@ TEST(GenerateTOML, TransferQuotedLiteralStrings) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(quot15 = '''Here are fifteen quotation marks: """""""""""""""''' + std::string ssTOMLInput = R"toml(quot15 = '''Here are fifteen quotation marks: """""""""""""""''' # apos15 = '''Here are fifteen apostrophes: '''''''''''''''''' # INVALID apos15 = "Here are fifteen apostrophes: '''''''''''''''" # 'That,' she said, 'is still pointless.' -str = ''''That,' she said, 'is still pointless.'''')code"; - std::string ssTOMLOutput = R"code([tree.branch] +str = ''''That,' she said, 'is still pointless.'''')toml"; + std::string ssTOMLOutput = R"toml([tree.branch] quot15 = '''Here are fifteen quotation marks: """""""""""""""''' # apos15 = '''Here are fifteen apostrophes: '''''''''''''''''' # INVALID apos15 = "Here are fifteen apostrophes: '''''''''''''''" # 'That,' she said, 'is still pointless.' -str = ''''That,' she said, 'is still pointless.'''')code"; +str = ''''That,' she said, 'is still pointless.'''')toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -610,15 +611,15 @@ TEST(GenerateTOML, TransferIntegers) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(int1 = +99 + std::string ssTOMLInput = R"toml(int1 = +99 int2 = 42 int3 = 0 -int4 = -17)code"; - std::string ssTOMLOutput = R"code([tree.branch] +int4 = -17)toml"; + std::string ssTOMLOutput = R"toml([tree.branch] int1 = +99 int2 = 42 int3 = 0 -int4 = -17)code"; +int4 = -17)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -632,15 +633,15 @@ TEST(GenerateTOML, TransferReadibleIntegers) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(int5 = 1_000 + std::string ssTOMLInput = R"toml(int5 = 1_000 int6 = 5_349_221 int7 = 53_49_221 # Indian number system grouping -int8 = 1_2_3_4_5 # VALID but discouraged)code"; - std::string ssTOMLOutput = R"code([tree.branch] +int8 = 1_2_3_4_5 # VALID but discouraged)toml"; + std::string ssTOMLOutput = R"toml([tree.branch] int5 = 1_000 int6 = 5_349_221 int7 = 53_49_221 # Indian number system grouping -int8 = 1_2_3_4_5 # VALID but discouraged)code"; +int8 = 1_2_3_4_5 # VALID but discouraged)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -654,7 +655,7 @@ TEST(GenerateTOML, TransferOtherBaseIntegers) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(# hexadecimal with prefix `0x` + std::string ssTOMLInput = R"toml(# hexadecimal with prefix `0x` hex1 = 0xDEADBEEF hex2 = 0xdeadbeef hex3 = 0xdead_beef @@ -664,8 +665,8 @@ oct1 = 0o01234567 oct2 = 0o755 # useful for Unix file permissions # binary with prefix `0b` -bin1 = 0b11010110)code"; - std::string ssTOMLOutput = R"code([tree.branch] +bin1 = 0b11010110)toml"; + std::string ssTOMLOutput = R"toml([tree.branch] # hexadecimal with prefix `0x` hex1 = 0xDEADBEEF hex2 = 0xdeadbeef @@ -676,7 +677,7 @@ oct1 = 0o01234567 oct2 = 0o755 # useful for Unix file permissions # binary with prefix `0b` -bin1 = 0b11010110)code"; +bin1 = 0b11010110)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -690,7 +691,7 @@ TEST(GenerateTOML, TransferFloatingPoints) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(# fractional + std::string ssTOMLInput = R"toml(# fractional flt1 = +1.0 flt2 = 3.1415 flt3 = -0.01 @@ -701,8 +702,8 @@ flt5 = 1e06 flt6 = -2E-2 # both -flt7 = 6.626e-34)code"; - std::string ssTOMLOutput = R"code([tree.branch] +flt7 = 6.626e-34)toml"; + std::string ssTOMLOutput = R"toml([tree.branch] # fractional flt1 = +1.0 flt2 = 3.1415 @@ -714,7 +715,7 @@ flt5 = 1e06 flt6 = -2E-2 # both -flt7 = 6.626e-34)code"; +flt7 = 6.626e-34)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -728,9 +729,9 @@ TEST(GenerateTOML, TransferReadibleFloatingPoints) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(flt8 = 224_617.445_991_228)code"; - std::string ssTOMLOutput = R"code([tree.branch] -flt8 = 224_617.445_991_228)code"; + std::string ssTOMLInput = R"toml(flt8 = 224_617.445_991_228)toml"; + std::string ssTOMLOutput = R"toml([tree.branch] +flt8 = 224_617.445_991_228)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -744,7 +745,7 @@ TEST(GenerateTOML, TransferSpecialFloatingPoints) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(# infinity + std::string ssTOMLInput = R"toml(# infinity sf1 = inf # positive infinity sf2 = +inf # positive infinity sf3 = -inf # negative infinity @@ -752,8 +753,8 @@ sf3 = -inf # negative infinity # not a number sf4 = nan # actual sNaN/qNaN encoding is implementation-specific sf5 = +nan # same as `nan` -sf6 = -nan # valid, actual encoding is implementation-specific)code"; - std::string ssTOMLOutput = R"code([tree.branch] +sf6 = -nan # valid, actual encoding is implementation-specific)toml"; + std::string ssTOMLOutput = R"toml([tree.branch] # infinity sf1 = inf # positive infinity sf2 = +inf # positive infinity @@ -762,7 +763,7 @@ sf3 = -inf # negative infinity # not a number sf4 = nan # actual sNaN/qNaN encoding is implementation-specific sf5 = +nan # same as `nan` -sf6 = -nan # valid, actual encoding is implementation-specific)code"; +sf6 = -nan # valid, actual encoding is implementation-specific)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -776,11 +777,11 @@ TEST(GenerateTOML, TransferBooleans) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(bool1 = true -bool2 = false)code"; - std::string ssTOMLOutput = R"code([tree.branch] + std::string ssTOMLInput = R"toml(bool1 = true +bool2 = false)toml"; + std::string ssTOMLOutput = R"toml([tree.branch] bool1 = true -bool2 = false)code"; +bool2 = false)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -794,13 +795,13 @@ TEST(GenerateTOML, DISABLED_TransferOffsetDateTimes) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(odt1 = 1979-05-27T07:32:00Z + std::string ssTOMLInput = R"toml(odt1 = 1979-05-27T07:32:00Z odt2 = 1979-05-27T00:32:00-07:00 -odt3 = 1979-05-27T00:32:00.999999-07:00)code"; - std::string ssTOMLOutput = R"code([tree.branch] +odt3 = 1979-05-27T00:32:00.999999-07:00)toml"; + std::string ssTOMLOutput = R"toml([tree.branch] odt1 = 1979-05-27T07:32:00Z odt2 = 1979-05-27T00:32:00-07:00 -odt3 = 1979-05-27T00:32:00.999999-07:00)code"; +odt3 = 1979-05-27T00:32:00.999999-07:00)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -814,9 +815,9 @@ TEST(GenerateTOML, DISABLED_TransferReadibleOffsetDateTimes) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(odt4 = 1979-05-27 07:32:00Z)code"; - std::string ssTOMLOutput = R"code([tree.branch] -odt4 = 1979-05-27 07:32:00Z)code"; + std::string ssTOMLInput = R"toml(odt4 = 1979-05-27 07:32:00Z)toml"; + std::string ssTOMLOutput = R"toml([tree.branch] +odt4 = 1979-05-27 07:32:00Z)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -830,11 +831,11 @@ TEST(GenerateTOML, DISABLED_TransferLocalDateTimes) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(ldt1 = 1979-05-27T07:32:00 -ldt2 = 1979-05-27T00:32:00.999999)code"; - std::string ssTOMLOutput = R"code([tree.branch] + std::string ssTOMLInput = R"toml(ldt1 = 1979-05-27T07:32:00 +ldt2 = 1979-05-27T00:32:00.999999)toml"; + std::string ssTOMLOutput = R"toml([tree.branch] ldt1 = 1979-05-27T07:32:00 -ldt2 = 1979-05-27T00:32:00.999999)code"; +ldt2 = 1979-05-27T00:32:00.999999)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -848,9 +849,9 @@ TEST(GenerateTOML, DISABLED_TransferLocalDates) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(ld1 = 1979-05-27)code"; - std::string ssTOMLOutput = R"code([tree.branch] -ld1 = 1979-05-27)code"; + std::string ssTOMLInput = R"toml(ld1 = 1979-05-27)toml"; + std::string ssTOMLOutput = R"toml([tree.branch] +ld1 = 1979-05-27)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -864,11 +865,11 @@ TEST(GenerateTOML, DISABLED_TransferLocalTimes) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(lt1 = 07:32:00 -lt2 = 00:32:00.999999)code"; - std::string ssTOMLOutput = R"code([tree.branch] + std::string ssTOMLInput = R"toml(lt1 = 07:32:00 +lt2 = 00:32:00.999999)toml"; + std::string ssTOMLOutput = R"toml([tree.branch] lt1 = 07:32:00 -lt2 = 00:32:00.999999)code"; +lt2 = 00:32:00.999999)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -882,7 +883,7 @@ TEST(GenerateTOML, TransferArrays) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(integers = [ 1, 2, 3 ] + std::string ssTOMLInput = R"toml(integers = [ 1, 2, 3 ] colors = [ "red", "yellow", "green" ] nested_arrays_of_ints = [ [ 1, 2 ], [3, 4, 5] ] nested_mixed_array = [ [ 1, 2 ], ["a", "b", "c"] ] @@ -893,8 +894,8 @@ numbers = [ 0.1, 0.2, 0.5, 1, 2, 5 ] contributors = [ "Foo Bar ", { name = "Baz Qux", email = "bazqux@example.com", url = "https://example.com/bazqux" } -])code"; - std::string ssTOMLOutput = R"code([tree.branch] +])toml"; + std::string ssTOMLOutput = R"toml([tree.branch] integers = [ 1, 2, 3 ] colors = [ "red", "yellow", "green" ] nested_arrays_of_ints = [ [ 1, 2 ], [3, 4, 5] ] @@ -906,7 +907,7 @@ numbers = [ 0.1, 0.2, 0.5, 1, 2, 5 ] contributors = [ "Foo Bar ", { name = "Baz Qux", email = "bazqux@example.com", url = "https://example.com/bazqux" } -])code"; +])toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -920,15 +921,15 @@ TEST(GenerateTOML, TransferMultiLineArrays) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(integers2 = [ + std::string ssTOMLInput = R"toml(integers2 = [ 1, 2, 3 ] integers3 = [ 1, 2, # this is ok -])code"; - std::string ssTOMLOutput = R"code([tree.branch] +])toml"; + std::string ssTOMLOutput = R"toml([tree.branch] integers2 = [ 1, 2, 3 ] @@ -936,7 +937,7 @@ integers2 = [ integers3 = [ 1, 2, # this is ok -])code"; +])toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -950,7 +951,7 @@ TEST(GenerateTOML, TransferTables) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code([table] + std::string ssTOMLInput = R"toml([table] [table-1] key1 = "some string" @@ -958,8 +959,8 @@ key2 = 123 [table-2] key1 = "another string" -key2 = 456)code"; - std::string ssTOMLOutput = R"code([tree.branch.table] +key2 = 456)toml"; + std::string ssTOMLOutput = R"toml([tree.branch.table] [tree.branch.table-1] key1 = "some string" @@ -967,7 +968,7 @@ key2 = 123 [tree.branch.table-2] key1 = "another string" -key2 = 456)code"; +key2 = 456)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -981,10 +982,10 @@ TEST(GenerateTOML, TransferQuotedKeyTables) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code([dog."tater.man"] -type.name = "pug")code"; - std::string ssTOMLOutput = R"code([tree.branch.dog."tater.man"] -type.name = "pug")code"; + std::string ssTOMLInput = R"toml([dog."tater.man"] +type.name = "pug")toml"; + std::string ssTOMLOutput = R"toml([tree.branch.dog."tater.man"] +type.name = "pug")toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -998,22 +999,22 @@ TEST(GenerateTOML, TransferWhitespaceKeyTables) { toml_parser::CParser parser; - std::string ssTOMLInput = u8R"code([a.b.c] # this is best practice + std::string ssTOMLInput = u8R"toml([a.b.c] # this is best practice x = 1 [ d.e.f ] # same as [d.e.f] y = 1 [ g . h . i ] # same as [g.h.i] z = 1 [ j . "ʞ" . 'l' ] # same as [j."ʞ".'l'] -a = 1)code"; - std::string ssTOMLOutput = u8R"code([tree.branch.a.b.c] # this is best practice +a = 1)toml"; + std::string ssTOMLOutput = u8R"toml([tree.branch.a.b.c] # this is best practice x = 1 [tree.branch. d.e.f ] # same as [d.e.f] y = 1 [tree.branch. g . h . i ] # same as [g.h.i] z = 1 [tree.branch. j . "ʞ" . 'l' ] # same as [j."ʞ".'l'] -a = 1)code"; +a = 1)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -1027,20 +1028,20 @@ TEST(GenerateTOML, TransferMixedOrderTables) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(# VALID BUT DISCOURAGED + std::string ssTOMLInput = R"toml(# VALID BUT DISCOURAGED [fruit.apple] a = 1 [animal] b = 2 [fruit.orange] -aa = 11)code"; - std::string ssTOMLOutput = R"code(# VALID BUT DISCOURAGED +aa = 11)toml"; + std::string ssTOMLOutput = R"toml(# VALID BUT DISCOURAGED [tree.branch.fruit.apple] a = 1 [tree.branch.animal] b = 2 [tree.branch.fruit.orange] -aa = 11)code"; +aa = 11)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -1054,15 +1055,15 @@ TEST(GenerateTOML, TransferMixedValueAndTables) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(# Top-level table begins. + std::string ssTOMLInput = R"toml(# Top-level table begins. name = "Fido" breed = "pug" # Top-level table ends. [owner] name = "Regina Dogman" -member_since = 1999)code"; - std::string ssTOMLOutput = R"code([tree.branch] +member_since = 1999)toml"; + std::string ssTOMLOutput = R"toml([tree.branch] # Top-level table begins. name = "Fido" breed = "pug" @@ -1070,7 +1071,7 @@ breed = "pug" # Top-level table ends. [tree.branch.owner] name = "Regina Dogman" -member_since = 1999)code"; +member_since = 1999)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -1084,14 +1085,14 @@ TEST(GenerateTOML, TransferAutomaticTables) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(fruit.apple.color = "red" -fruit.apple.taste.sweet = true)code"; - std::string ssTOMLOutput = R"code([tree.branch] + std::string ssTOMLInput = R"toml(fruit.apple.color = "red" +fruit.apple.taste.sweet = true)toml"; + std::string ssTOMLOutput = R"toml([tree.branch] fruit.apple.color = "red" -fruit.apple.taste.sweet = true)code"; - std::string ssTOMLOutput2 = R"code([tree.branch] +fruit.apple.taste.sweet = true)toml"; + std::string ssTOMLOutput2 = R"toml([tree.branch] apple.color = "red" -apple.taste.sweet = true)code"; +apple.taste.sweet = true)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -1107,7 +1108,7 @@ TEST(GenerateTOML, TransferMixedAutomaticTables) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code([fruit] + std::string ssTOMLInput = R"toml([fruit] apple.color = "red" apple.taste.sweet = true @@ -1115,8 +1116,8 @@ apple.taste.sweet = true # [fruit.apple.taste] # INVALID [fruit.apple.texture] # you can add sub-tables -smooth = true)code"; - std::string ssTOMLOutput = R"code([tree.branch.fruit] +smooth = true)toml"; + std::string ssTOMLOutput = R"toml([tree.branch.fruit] apple.color = "red" apple.taste.sweet = true @@ -1124,7 +1125,7 @@ apple.taste.sweet = true # [fruit.apple.taste] # INVALID [tree.branch.fruit.apple.texture] # you can add sub-tables -smooth = true)code"; +smooth = true)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -1138,13 +1139,13 @@ TEST(GenerateTOML, TransferInlineTables) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(name = { first = "Tom", last = "Preston-Werner" } + std::string ssTOMLInput = R"toml(name = { first = "Tom", last = "Preston-Werner" } point = { x = 1, y = 2 } -animal = { type.name = "pug" })code"; - std::string ssTOMLOutput = R"code([tree.branch] +animal = { type.name = "pug" })toml"; + std::string ssTOMLOutput = R"toml([tree.branch] name = { first = "Tom", last = "Preston-Werner" } point = { x = 1, y = 2 } -animal = { type.name = "pug" })code"; +animal = { type.name = "pug" })toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; @@ -1157,13 +1158,13 @@ TEST(GenerateTOML, TransferEmbeddedInlineTables) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(test=[{ first = "Tom", last = "Preston-Werner" }, + std::string ssTOMLInput = R"toml(test=[{ first = "Tom", last = "Preston-Werner" }, { x = 1, y = 2 }, -{ type.name = "pug" }])code"; - std::string ssTOMLOutput = R"code([tree.branch] +{ type.name = "pug" }])toml"; + std::string ssTOMLOutput = R"toml([tree.branch] test=[{ first = "Tom", last = "Preston-Werner" }, { x = 1, y = 2 }, -{ type.name = "pug" }])code"; +{ type.name = "pug" }])toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -1177,7 +1178,7 @@ TEST(GenerateTOML, TransferTableArrays) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code([[products]] + std::string ssTOMLInput = R"toml([[products]] name = "Hammer" sku = 738594937 @@ -1187,8 +1188,8 @@ sku = 738594937 name = "Nail" sku = 284758393 -color = "gray")code"; - std::string ssTOMLOutput = R"code([[tree.branch.products]] +color = "gray")toml"; + std::string ssTOMLOutput = R"toml([[tree.branch.products]] name = "Hammer" sku = 738594937 @@ -1198,7 +1199,7 @@ sku = 738594937 name = "Nail" sku = 284758393 -color = "gray")code"; +color = "gray")toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -1212,7 +1213,7 @@ TEST(GenerateTOML, TransferMixedTableAndTableArrays) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code([[fruits]] + std::string ssTOMLInput = R"toml([[fruits]] name = "apple" [fruits.physical] # subtable @@ -1230,8 +1231,8 @@ name = "granny smith" name = "banana" [[fruits.varieties]] -name = "plantain")code"; - std::string ssTOMLOutput = R"code([[tree.branch.fruits]] +name = "plantain")toml"; + std::string ssTOMLOutput = R"toml([[tree.branch.fruits]] name = "apple" [tree.branch.fruits.physical] # subtable @@ -1249,7 +1250,7 @@ name = "granny smith" name = "banana" [[tree.branch.fruits.varieties]] -name = "plantain")code"; +name = "plantain")toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -1263,13 +1264,13 @@ TEST(GenerateTOML, TransferInlineTableArrays) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(points = [ { x = 1, y = 2, z = 3 }, + std::string ssTOMLInput = R"toml(points = [ { x = 1, y = 2, z = 3 }, { x = 7, y = 8, z = 9 }, - { x = 2, y = 4, z = 8 } ])code"; - std::string ssTOMLOutput = R"code([tree.branch] + { x = 2, y = 4, z = 8 } ])toml"; + std::string ssTOMLOutput = R"toml([tree.branch] points = [ { x = 1, y = 2, z = 3 }, { x = 7, y = 8, z = 9 }, - { x = 2, y = 4, z = 8 } ])code"; + { x = 2, y = 4, z = 8 } ])toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; diff --git a/tests/unit_tests/toml_parser/generate_toml_getset_comment.cpp b/tests/unit_tests/toml_parser/getset_comment.cpp similarity index 83% rename from tests/unit_tests/toml_parser/generate_toml_getset_comment.cpp rename to tests/unit_tests/toml_parser/getset_comment.cpp index 8597a17..eb432b9 100644 --- a/tests/unit_tests/toml_parser/generate_toml_getset_comment.cpp +++ b/tests/unit_tests/toml_parser/getset_comment.cpp @@ -12,15 +12,15 @@ ********************************************************************************/ #include - +#include "../../../global/localmemmgr.h" #include "../../../sdv_services/core/toml_parser/parser_node_toml.h" #include "../../../sdv_services/core/toml_parser/parser_toml.h" -TEST(GenerateTOML, GetCommentRoot) +TEST(GetSetComment, GetCommentRoot) { toml_parser::CParser parser; - std::string ssTOML = R"code(# This is a comment)code"; + std::string ssTOML = R"toml(# This is a comment)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -34,17 +34,17 @@ TEST(GenerateTOML, GetCommentRoot) EXPECT_EQ(ssComment, "This is a comment"); } -TEST(GenerateTOML, GetMultiLineCommentRoot) +TEST(GetSetComment, GetMultiLineCommentRoot) { toml_parser::CParser parser; - std::string ssTOML = R"code( + std::string ssTOML = R"toml( # This is a comment that stretches # more than one line. # And here's another comment of more # than one line. -)code"; +)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); @@ -55,23 +55,23 @@ TEST(GenerateTOML, GetMultiLineCommentRoot) ssComment = parser.Root().GetComment(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_before); EXPECT_TRUE(ssComment.empty()); ssComment = parser.Root().GetComment(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_behind); - EXPECT_EQ(ssComment, R"code(This is a comment that stretches more than one line. -And here's another comment of more than one line.)code"); + EXPECT_EQ(ssComment, R"toml(This is a comment that stretches more than one line. +And here's another comment of more than one line.)toml"); } -TEST(GenerateTOML, SetCommentRoot) +TEST(GetSetComment, SetCommentRoot) { toml_parser::CParser parser; // This will test the SetComment function and overwriting the existing comment. - std::string ssTOMLInput = R"code(# This is a double line comment -# This is line two of the double line comment)code"; - std::string ssTOMLOutput = R"code(# This is comment way before + std::string ssTOMLInput = R"toml(# This is a double line comment +# This is line two of the double line comment)toml"; + std::string ssTOMLOutput = R"toml(# This is comment way before # This is comment before # This is comment way behind -)code"; +)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -93,13 +93,13 @@ TEST(GenerateTOML, SetCommentRoot) EXPECT_EQ(ssGenerated, ssTOMLOutput); } -TEST(GenerateTOML, SetMultiLineCommentRoot) +TEST(GetSetComment, SetMultiLineCommentRoot) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(# This is a double line comment -# This is line two of the double line comment)code"; - std::string ssTOMLOutput = R"code(# This is comment way before + std::string ssTOMLInput = R"toml(# This is a double line comment +# This is line two of the double line comment)toml"; + std::string ssTOMLOutput = R"toml(# This is comment way before # And surprise, also a second line @@ -109,7 +109,7 @@ TEST(GenerateTOML, SetMultiLineCommentRoot) # This is comment way behind # And final, also a second line -)code"; +)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -139,13 +139,13 @@ And final, also a second line)"); EXPECT_EQ(ssGenerated, ssTOMLOutput); } -TEST(GenerateTOML, RemoveCommentRoot) +TEST(GetSetComment, RemoveCommentRoot) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(# This is a double line comment -# This is line two of the double line comment)code"; - std::string ssTOMLOutput = R"code()code"; + std::string ssTOMLInput = R"toml(# This is a double line comment +# This is line two of the double line comment)toml"; + std::string ssTOMLOutput = R"toml()toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -155,16 +155,16 @@ TEST(GenerateTOML, RemoveCommentRoot) EXPECT_EQ(ssGenerated, ssTOMLOutput); } -TEST(GenerateTOML, GetCommentRootValue) +TEST(GetSetComment, GetCommentRootValue) { toml_parser::CParser parser; - std::string ssTOML = R"code(# This is a separate comment + std::string ssTOML = R"toml(# This is a separate comment # This is a comment before the value value = "this is the value text" # Comment following the value -# This is also a separate comment)code"; +# This is also a separate comment)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); sdv::TInterfaceAccessPtr ptrValue = parser.Root().GetNodeDirect("value"); @@ -182,11 +182,11 @@ value = "this is the value text" # Comment following the value EXPECT_EQ(ssComment, "This is also a separate comment"); } -TEST(GenerateTOML, GetMultiLineCommentRootValue) +TEST(GetSetComment, GetMultiLineCommentRootValue) { toml_parser::CParser parser; - std::string ssTOML = R"code( + std::string ssTOML = R"toml( # This is a separate comment with several line-breaks before. @@ -209,7 +209,7 @@ value = "this is the value text" # Comment following the value. # This is also a separate comment. # Followed by this text on the same line. -# And another text on a separate line.)code"; +# And another text on a separate line.)toml"; EXPECT_NO_THROW(parser.Process(ssTOML)); sdv::TInterfaceAccessPtr ptrValue = parser.Root().GetNodeDirect("value"); @@ -218,32 +218,32 @@ value = "this is the value text" # Comment following the value. ASSERT_NE(pComment, nullptr); std::string ssComment = pComment->GetComment(sdv::toml::INodeInfo::ECommentType::comment_before); - EXPECT_EQ(ssComment, R"code(This is a comment before the value. And another comment before the value at the same line. -Note: there was a space after the empty comment line. But that should not influence the comment lines.)code"); + EXPECT_EQ(ssComment, R"toml(This is a comment before the value. And another comment before the value at the same line. +Note: there was a space after the empty comment line. But that should not influence the comment lines.)toml"); ssComment = pComment->GetComment(sdv::toml::INodeInfo::ECommentType::comment_behind); - EXPECT_EQ(ssComment, R"code(Comment following the value. More comment following the value. This becomes one line. -But this is a separate line.)code"); + EXPECT_EQ(ssComment, R"toml(Comment following the value. More comment following the value. This becomes one line. +But this is a separate line.)toml"); ssComment = pComment->GetComment(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_before); - EXPECT_EQ(ssComment, R"code(This is a separate comment with several line-breaks before. Followed by this text on the same line. -Note: there was a space after the empty comment line. And another separate comment on a next line.)code"); + EXPECT_EQ(ssComment, R"toml(This is a separate comment with several line-breaks before. Followed by this text on the same line. +Note: there was a space after the empty comment line. And another separate comment on a next line.)toml"); ssComment = pComment->GetComment(sdv::toml::INodeInfo::ECommentType::out_of_scope_comment_behind); - EXPECT_EQ(ssComment, R"code(This is also a separate comment. Followed by this text on the same line. -And another text on a separate line.)code"); + EXPECT_EQ(ssComment, R"toml(This is also a separate comment. Followed by this text on the same line. +And another text on a separate line.)toml"); } -TEST(GenerateTOML, SetCommentRootValue) +TEST(GetSetComment, SetCommentRootValue) { toml_parser::CParser parser; // This will test the SetComment function and overwriting the existing comment. - std::string ssTOMLInput = R"code(# This is a double line comment -# This is line two of the double line comment)code"; - std::string ssTOMLOutput = R"code(# This is comment #3 + std::string ssTOMLInput = R"toml(# This is a double line comment +# This is line two of the double line comment)toml"; + std::string ssTOMLOutput = R"toml(# This is comment #3 # This is comment #1 # This is comment #4 -)code"; +)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -265,13 +265,13 @@ TEST(GenerateTOML, SetCommentRootValue) EXPECT_EQ(ssGenerated, ssTOMLOutput); } -TEST(GenerateTOML, SetMultiLineCommentRootValue) +TEST(GetSetComment, SetMultiLineCommentRootValue) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(# This is a double line comment -# This is line two of the double line comment)code"; - std::string ssTOMLOutput = R"code(# This is comment #3 + std::string ssTOMLInput = R"toml(# This is a double line comment +# This is line two of the double line comment)toml"; + std::string ssTOMLOutput = R"toml(# This is comment #3 # And surprise, also a second line @@ -281,7 +281,7 @@ TEST(GenerateTOML, SetMultiLineCommentRootValue) # This is comment #4 # And final, also a second line -)code"; +)toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); @@ -311,13 +311,13 @@ And final, also a second line)"); EXPECT_EQ(ssGenerated, ssTOMLOutput); } -TEST(GenerateTOML, RemoveCommentRootValue) +TEST(GetSetComment, RemoveCommentRootValue) { toml_parser::CParser parser; - std::string ssTOMLInput = R"code(# This is a double line comment -# This is line two of the double line comment)code"; - std::string ssTOMLOutput = R"code()code"; + std::string ssTOMLInput = R"toml(# This is a double line comment +# This is line two of the double line comment)toml"; + std::string ssTOMLOutput = R"toml()toml"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); diff --git a/tests/unit_tests/toml_parser/indexer_tests.cpp b/tests/unit_tests/toml_parser/indexer_tests.cpp new file mode 100644 index 0000000..6639f4c --- /dev/null +++ b/tests/unit_tests/toml_parser/indexer_tests.cpp @@ -0,0 +1,119 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Martin Stimpfl - initial API and implementation + * Erik Verhoeven - writing TOML and whitespace preservation + ********************************************************************************/ + +#include +#include "../../../global/localmemmgr.h" +#include "../../../sdv_services/core/toml_parser/parser_node_indexer.h" + +TEST(IndexerTest, InitializeSingle) +{ + toml_parser::CNodeIndexer indexer; + + EXPECT_NO_THROW(toml_parser::CNodeIndex index = indexer.CreateIndex()); +} + +TEST(IndexerTest, CompareOrder) +{ + toml_parser::CNodeIndexer indexer; + auto index1 = indexer.CreateIndex(); + auto index2 = indexer.CreateIndex(); + auto index3 = indexer.CreateIndex(); + EXPECT_GT(index2, index1); + EXPECT_GT(index3, index1); + EXPECT_GT(index3, index2); +} + +TEST(IndexerTest, SwitchOrder) +{ + toml_parser::CNodeIndexer indexer; + auto index1 = indexer.CreateIndex(); + auto index2 = indexer.CreateIndex(); + auto index3 = indexer.CreateIndex(); + index3.MoveBefore(index1); + EXPECT_GT(index2, index1); + EXPECT_LT(index3, index1); + EXPECT_LT(index3, index2); +} + +TEST(IndexerTest, SwitchOrderMultipleIndexer) +{ + toml_parser::CNodeIndexer indexer1; + toml_parser::CNodeIndexer indexer2; + toml_parser::CNodeIndexer indexer3; + auto index1 = indexer1.CreateIndex(); + auto index2 = indexer2.CreateIndex(); + auto index3 = indexer3.CreateIndex(); + EXPECT_GT(index2, index1); + EXPECT_GT(index3, index1); + EXPECT_GT(index3, index2); + index3.MoveBefore(index1); + EXPECT_GT(index2, index1); + EXPECT_LT(index3, index1); + EXPECT_LT(index3, index2); + index2.MoveBefore(index3); + EXPECT_LT(index2, index1); + EXPECT_LT(index3, index1); + EXPECT_GT(index3, index2); +} + +TEST(IndexerTest, IndexLifetime) +{ + toml_parser::CNodeIndexer indexer; + + auto index1 = indexer.CreateIndex(); + size_t nCurrentCnt = indexer.Count(); + { + auto index2 = indexer.CreateIndex(); + EXPECT_GT(index2, index1); + EXPECT_EQ(indexer.Count(), nCurrentCnt + 1); + } + EXPECT_EQ(indexer.Count(), nCurrentCnt); + auto index3 = indexer.CreateIndex(); + EXPECT_GT(index3, index1); +} + +TEST(IndexerTest, CopyIndex) +{ + toml_parser::CNodeIndexer indexer; + + auto index1 = indexer.CreateIndex(); + auto index2 = indexer.CreateIndex(); + auto index2b = index2; + EXPECT_TRUE(index2b); + EXPECT_TRUE(index2); + EXPECT_GT(index2b, index1); + EXPECT_GT(index2, index1); + EXPECT_EQ(index2, index2b); + auto index3 = indexer.CreateIndex(); + EXPECT_GT(index3, index2); + EXPECT_GT(index3, index1); +} + +TEST(IndexerTest, MoveIndex) +{ + toml_parser::CNodeIndexer indexer; + + auto index1 = indexer.CreateIndex(); + auto index2 = indexer.CreateIndex(); + auto index2b = std::move(index2); + EXPECT_TRUE(index2b); + EXPECT_FALSE(index2); + EXPECT_GT(index2b, index1); + EXPECT_GT(index2, index1); // index 2 is not initialized any more. + EXPECT_GT(index2, index2b); + auto index3 = indexer.CreateIndex(); + EXPECT_GT(index3, index2b); + EXPECT_GT(index3, index1); + EXPECT_GT(index2, index3); +} diff --git a/tests/unit_tests/toml_parser/insert_node.cpp b/tests/unit_tests/toml_parser/insert_node.cpp new file mode 100644 index 0000000..a0d9682 --- /dev/null +++ b/tests/unit_tests/toml_parser/insert_node.cpp @@ -0,0 +1,990 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#include +#include "../../../global/localmemmgr.h" +#include "../../../sdv_services/core/toml_parser/parser_node_toml.h" +#include "../../../sdv_services/core/toml_parser/parser_toml.h" +#include + +// Test TODO: +// - Inserted and straight away deleted +// - Inserted with false/deleted reference --error +// - Inserted values before (okay) and behind (error) tables +// - Inserted duplicate value -- error +// - Smart insert (comments/whitespace around) +// Insert as TOML, but only partly correct. + +TEST(InsertNode, InsertValuesRoot) +{ + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + EXPECT_TRUE(root.InsertValue("", "value_int", 10)); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(value_int = 10)toml"); + EXPECT_TRUE(root.InsertValue("", "value_str", u8"abc")); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(value_int = 10 +value_str = "abc")toml"); + EXPECT_NE(parser.Root().InsertValue(root.GetNodeNameByIndex(0), "value_float", 123.456), nullptr); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(value_float = 123.456 +value_int = 10 +value_str = "abc")toml"); +} + +TEST(InsertNode, InsertTableRoot) +{ + // Insert a standard table + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + EXPECT_TRUE(root.InsertTable("", "table1", false)); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([table1])toml"); + + // Insert an inline table before + EXPECT_TRUE(root.InsertTable(root.GetNodeNameByIndex(0), "table2", true)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(table2 = {} +[table1])toml"); + + // Insert an inline table behind -> this will have to be printed before the standard table + EXPECT_TRUE(root.InsertTable("", "table3", true)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(table2 = {} +table3 = {} +[table1])toml"); + + // Insert a standard table in front -> this will have to be printed behind the inline table + EXPECT_TRUE(root.InsertTable(root.GetNodeNameByIndex(0), "table4", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(table2 = {} +table3 = {} +[table4] +[table1])toml"); +} + +TEST(InsertNode, InsertArrayRoot) +{ + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + EXPECT_TRUE(root.InsertArray("", "value_array1")); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(value_array1 = [])toml"); + EXPECT_TRUE(root.InsertArray("", "value_array2")); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(value_array1 = [] +value_array2 = [])toml"); + EXPECT_TRUE(root.InsertArray(root.GetNodeNameByIndex(0), "value_array3")); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(value_array3 = [] +value_array1 = [] +value_array2 = [])toml"); +} + +TEST(InsertNode, InsertTableArrayRoot) +{ + // Insert a standard table array + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + EXPECT_TRUE(root.InsertTableArray("", "table_array1", false)); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array1]])toml"); + + // Insert an inline table array before + EXPECT_TRUE(root.InsertTableArray(root.GetNodeNameByIndex(0), "table_array2", true)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(table_array2 = [{}] +[[table_array1]])toml"); + + // Insert an inline table array behind -> this will have to be printed before the standard table array + EXPECT_TRUE(root.InsertTableArray("", "table_array3", true)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(table_array2 = [{}] +table_array3 = [{}] +[[table_array1]])toml"); + + // Insert a standard table array in front -> this will have to be printed behind the inline table array + EXPECT_TRUE(root.InsertTableArray(root.GetNodeNameByIndex(0), "table_array4", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(table_array2 = [{}] +table_array3 = [{}] +[[table_array4]] +[[table_array1]])toml"); + + // Add an additional table array entry for table array #4 + EXPECT_TRUE(root.InsertTableArray("", "table_array4", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(table_array2 = [{}] +table_array3 = [{}] +[[table_array4]] +[[table_array1]] +[[table_array4]])toml"); +} + +TEST(InsertNode, InsertValueInStandardTable) +{ + // Insert a standard table + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + sdv::toml::CNodeCollection tableStandard = root.InsertTable("", "standard_table", false); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table])toml"); + + // Insert the values into the table + EXPECT_TRUE(tableStandard.InsertValue("", "value_int", 10)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table] +value_int = 10)toml"); + EXPECT_TRUE(tableStandard.InsertValue("", "value_str", u8"abc")); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table] +value_int = 10 +value_str = "abc")toml"); + EXPECT_TRUE(tableStandard.InsertValue(tableStandard.GetNodeNameByIndex(0), "value_float", 123.456)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table] +value_float = 123.456 +value_int = 10 +value_str = "abc")toml"); +} + +TEST(InsertNode, InsertTableInStandardTable) +{ + // Insert a standard table + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + sdv::toml::CNodeCollection tableStandard = root.InsertTable("", "standard_table", false); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table])toml"); + + // Insert a standard table + EXPECT_TRUE(tableStandard.InsertTable("", "table1", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table.table1])toml"); + + // Insert an inline table before + EXPECT_TRUE(tableStandard.InsertTable(tableStandard.GetNodeNameByIndex(0), "table2", true)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table] +table2 = {} +[standard_table.table1])toml"); + + // Insert an inline table behind -> this will have to be printed before the standard table + EXPECT_TRUE(tableStandard.InsertTable("", "table3", true)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table] +table2 = {} +table3 = {} +[standard_table.table1])toml"); + + // Insert a standard table in front -> this will have to be printed behind the inline table relative to the elements of the + // standard_table. + EXPECT_TRUE(tableStandard.InsertTable(tableStandard.GetNodeNameByIndex(0), "table4", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table] +table2 = {} +table3 = {} +[standard_table.table4] +[standard_table.table1])toml"); +} + +TEST(InsertNode, InsertArrayInStandardTable) +{ + // Insert a standard table + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + sdv::toml::CNodeCollection tableStandard = root.InsertTable("", "standard_table", false); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table])toml"); + + // Insert arrays + EXPECT_TRUE(tableStandard.InsertArray("", "value_array1")); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table] +value_array1 = [])toml"); + EXPECT_TRUE(tableStandard.InsertArray("", "value_array2")); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table] +value_array1 = [] +value_array2 = [])toml"); + EXPECT_TRUE(tableStandard.InsertArray(tableStandard.GetNodeNameByIndex(0), "value_array3")); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table] +value_array3 = [] +value_array1 = [] +value_array2 = [])toml"); +} + +TEST(InsertNode, InsertTableArrayInStandardTable) +{ + // Insert a standard table + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + sdv::toml::CNodeCollection tableStandard = root.InsertTable("", "standard_table", false); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table])toml"); + + // Insert standard table array + EXPECT_TRUE(tableStandard.InsertTableArray("", "table_array1", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[standard_table.table_array1]])toml"); + + // Insert an inline table array before + EXPECT_TRUE(tableStandard.InsertTableArray(tableStandard.GetNodeNameByIndex(0), "table_array2", true)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table] +table_array2 = [{}] +[[standard_table.table_array1]])toml"); + + // Insert an inline table array behind -> this will have to be printed before the standard table array + EXPECT_TRUE(tableStandard.InsertTableArray("", "table_array3", true)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table] +table_array2 = [{}] +table_array3 = [{}] +[[standard_table.table_array1]])toml"); + + // Insert a standard table array in front -> this will have to be printed behind the inline table array relative to the elements + // of the standard_table. + EXPECT_TRUE(tableStandard.InsertTableArray(tableStandard.GetNodeNameByIndex(0), "table_array4", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table] +table_array2 = [{}] +table_array3 = [{}] +[[standard_table.table_array4]] +[[standard_table.table_array1]])toml"); + + // Add an additional table array entry for table array #4 + EXPECT_TRUE(tableStandard.InsertTableArray("", "table_array4", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([standard_table] +table_array2 = [{}] +table_array3 = [{}] +[[standard_table.table_array4]] +[[standard_table.table_array1]] +[[standard_table.table_array4]])toml"); +} + +TEST(InsertNode, InsertValueInInlineTable) +{ + // Insert an inline table + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + sdv::toml::CNodeCollection tableInline = root.InsertTable("", "inline_table", true); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {})toml"); + + // Insert the values into the table + EXPECT_TRUE(tableInline.InsertValue("", "value_int", 10)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {value_int = 10})toml"); + EXPECT_TRUE(tableInline.InsertValue("", "value_str", u8"abc")); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {value_int = 10, value_str = "abc"})toml"); + EXPECT_TRUE(tableInline.InsertValue(tableInline.GetNodeNameByIndex(0), "value_float", 123.456)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {value_float = 123.456, value_int = 10, value_str = "abc"})toml"); +} + +TEST(InsertNode, InsertTableInInlineTable) +{ + // Insert an inline table + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + sdv::toml::CNodeCollection tableInline = root.InsertTable("", "inline_table", true); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {})toml"); + + // Insert a standard table + EXPECT_TRUE(tableInline.InsertTable("", "table1", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {table1 = {}})toml"); + + // Insert an inline table before + EXPECT_TRUE(tableInline.InsertTable(tableInline.GetNodeNameByIndex(0), "table2", true)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {table2 = {}, table1 = {}})toml"); + + // Insert an inline table behind -> this will have to be printed before the standard table + EXPECT_TRUE(tableInline.InsertTable("", "table3", + true)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {table2 = {}, table1 = {}, table3 = {}})toml"); + + // Insert a standard table in front -> this will have to be printed behind the inline table + EXPECT_TRUE(tableInline.InsertTable(tableInline.GetNodeNameByIndex(0), "table4", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {table4 = {}, table2 = {}, table1 = {}, table3 = {}})toml"); +} + +TEST(InsertNode, InsertArrayInInlineTable) +{ + // Insert an inline table + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + sdv::toml::CNodeCollection tableInline = root.InsertTable("", "inline_table", true); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {})toml"); + + // Insert arrays + EXPECT_TRUE(tableInline.InsertArray("", "value_array1")); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {value_array1 = []})toml"); + EXPECT_TRUE(tableInline.InsertArray("", "value_array2")); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {value_array1 = [], value_array2 = []})toml"); + EXPECT_TRUE(tableInline.InsertArray(tableInline.GetNodeNameByIndex(0), "value_array3")); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {value_array3 = [], value_array1 = [], value_array2 = []})toml"); +} + +TEST(InsertNode, InsertTableArrayInInlineTable) +{ + // Insert an inline table + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + sdv::toml::CNodeCollection tableInline = root.InsertTable("", "inline_table", true); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {})toml"); + + // Insert standard table array + EXPECT_TRUE(tableInline.InsertTableArray("", "table_array1", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {table_array1 = [{}]})toml"); + + // Insert an inline table array before + EXPECT_TRUE(tableInline.InsertTableArray(tableInline.GetNodeNameByIndex(0), "table_array2", true)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {table_array2 = [{}], table_array1 = [{}]})toml"); + + // Insert an inline table array behind -> this will have to be printed before the standard table array + EXPECT_TRUE(tableInline.InsertTableArray("", "table_array3", true)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_table = {table_array2 = [{}], table_array1 = [{}], table_array3 = [{}]})toml"); + + // Insert a standard table array in front -> this will have to be printed behind the inline table array + EXPECT_TRUE(tableInline.InsertTableArray(tableInline.GetNodeNameByIndex(0), "table_array4", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, + R"toml(inline_table = {table_array4 = [{}], table_array2 = [{}], table_array1 = [{}], table_array3 = [{}]})toml"); + + // Add an additional table array entry for table array #4 + EXPECT_TRUE(tableInline.InsertTableArray("", "table_array4", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, + R"toml(inline_table = {table_array4 = [{}, {}], table_array2 = [{}], table_array1 = [{}], table_array3 = [{}]})toml"); +} + +TEST(InsertNode, InsertValueInArray) +{ + // Insert an array + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + sdv::toml::CNodeCollection arrayInline = root.InsertArray("", "inline_array"); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [])toml"); + + // Insert the values into the table (with or without name) + EXPECT_TRUE(arrayInline.InsertValue("", "", 10)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [10])toml"); + EXPECT_TRUE(arrayInline.InsertValue("", "", u8"abc")); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [10, "abc"])toml"); + EXPECT_TRUE(arrayInline.InsertValue(arrayInline.GetNodeNameByIndex(0), "", 123.456)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [123.456, 10, "abc"])toml"); +} + +TEST(InsertNode, InsertTableInArray) +{ + // Insert an array + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + sdv::toml::CNodeCollection arrayInline = root.InsertArray("", "inline_array"); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [])toml"); + + // Insert a standard table + sdv::toml::CNodeCollection table = arrayInline.InsertTable("", "", false); + EXPECT_TRUE(table); + table.InsertValue("", "a", 10); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [{a = 10}])toml"); + + // Insert an inline table before + table = arrayInline.InsertTable(arrayInline.GetNodeNameByIndex(0), "", true); + EXPECT_TRUE(table); + table.InsertValue("", "b", 20); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [{b = 20}, {a = 10}])toml"); + + // Insert an inline table behind + table = arrayInline.InsertTable("", "", true); + EXPECT_TRUE(table); + table.InsertValue("", "c", 30); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [{b = 20}, {a = 10}, {c = 30}])toml"); + + // Insert a standard table in front + table = arrayInline.InsertTable(arrayInline.GetNodeNameByIndex(0), "", false); + EXPECT_TRUE(table); + table.InsertValue("", "d", 40); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [{d = 40}, {b = 20}, {a = 10}, {c = 30}])toml"); +} + +TEST(InsertNode, InsertArrayInArray) +{ + // Insert an array + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + sdv::toml::CNodeCollection arrayInline = root.InsertArray("", "inline_array"); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [])toml"); + + // Insert arrays + sdv::toml::CNodeCollection array = arrayInline.InsertArray("", ""); + EXPECT_TRUE(array); + array.InsertValue("", "", 10); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [[10]])toml"); + array = arrayInline.InsertArray("", ""); + EXPECT_TRUE(array); + array.InsertValue("", "", 20); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [[10], [20]])toml"); + array = arrayInline.InsertArray(arrayInline.GetNodeNameByIndex(0), ""); + EXPECT_TRUE(array); + array.InsertValue("", "", 30); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [[30], [10], [20]])toml"); +} + +TEST(InsertNode, InsertTableArrayInArray) +{ + // Insert an array + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + sdv::toml::CNodeCollection arrayInline = root.InsertArray("", "inline_array"); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [])toml"); + + // Insert standard table array + sdv::toml::CNodeCollection table = arrayInline.InsertTableArray("", "", false); + EXPECT_TRUE(table); + table.InsertValue("", "a", 10); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [[{a = 10}]])toml"); + + // Insert an inline table array before + table = arrayInline.InsertTableArray(arrayInline.GetNodeNameByIndex(0), "", true); + EXPECT_TRUE(table); + table.InsertValue("", "b", 20); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [[{b = 20}], [{a = 10}]])toml"); + + // Insert an inline table array behind -> this will have to be printed before the standard table array + table = arrayInline.InsertTableArray("", "", true); + EXPECT_TRUE(table); + table.InsertValue("", "c", 30); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [[{b = 20}], [{a = 10}], [{c = 30}]])toml"); + + // Insert a standard table array in front -> this will have to be printed behind the inline table array + table = arrayInline.InsertTableArray(arrayInline.GetNodeNameByIndex(0), "", false); + EXPECT_TRUE(table); + table.InsertValue("", "d", 40); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [[{d = 40}], [{b = 20}], [{a = 10}], [{c = 30}]])toml"); + + // Add an additional table array entry for table array #4 + sdv::toml::CNodeCollection array = arrayInline.Get(0); + EXPECT_TRUE(array); + table = array.InsertTable("", "", false); + EXPECT_TRUE(table); + table.InsertValue("", "e", 50); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(inline_array = [[{d = 40}, {e = 50}], [{b = 20}], [{a = 10}], [{c = 30}]])toml"); +} + +TEST(InsertNode, InsertValueInTableArray) +{ + // Insert a standard table array + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + sdv::toml::CNodeCollection table = root.InsertTableArray("", "table_array", false); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]])toml"); + + // Insert the values into the table + EXPECT_TRUE(table.InsertValue("", "value_int", 10)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +value_int = 10)toml"); + EXPECT_TRUE(table.InsertValue("", "value_str", u8"abc")); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +value_int = 10 +value_str = "abc")toml"); + EXPECT_TRUE(table.InsertValue(table.GetNodeNameByIndex(0), "value_float", 123.456)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +value_float = 123.456 +value_int = 10 +value_str = "abc")toml"); +} + +TEST(InsertNode, InsertTableInTableArray) +{ + // Insert a standard table array + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + sdv::toml::CNodeCollection table = root.InsertTableArray("", "table_array", false); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]])toml"); + + // Insert a standard table + EXPECT_TRUE(table.InsertTable("", "table1", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +[table_array.table1])toml"); + + // Insert an inline table before + EXPECT_TRUE(table.InsertTable(table.GetNodeNameByIndex(0), "table2", true)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +table2 = {} +[table_array.table1])toml"); + + // Insert an inline table behind -> this will have to be printed before the standard table + EXPECT_TRUE(table.InsertTable("", "table3", true)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +table2 = {} +table3 = {} +[table_array.table1])toml"); + + // Insert a standard table in front -> this will have to be printed behind the inline table + EXPECT_TRUE(table.InsertTable(table.GetNodeNameByIndex(0), "table4", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +table2 = {} +table3 = {} +[table_array.table4] +[table_array.table1])toml"); +} + +TEST(InsertNode, InsertArrayInTableArray) +{ + // Insert a standard table array + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + sdv::toml::CNodeCollection table = root.InsertTableArray("", "table_array", false); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]])toml"); + + // Insert arrays + EXPECT_TRUE(table.InsertArray("", "value_array1")); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +value_array1 = [])toml"); + EXPECT_TRUE(table.InsertArray("", "value_array2")); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +value_array1 = [] +value_array2 = [])toml"); + EXPECT_TRUE(table.InsertArray(table.GetNodeNameByIndex(0), "value_array3")); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +value_array3 = [] +value_array1 = [] +value_array2 = [])toml"); +} + +TEST(InsertNode, InsertTableArrayInTableArray) +{ + // Insert a standard table array + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + sdv::toml::CNodeCollection table = root.InsertTableArray("", "table_array", false); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]])toml"); + + // Insert standard table array + EXPECT_TRUE(table.InsertTableArray("", "table_array1", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +[[table_array.table_array1]])toml"); + + // Insert an inline table array before + EXPECT_TRUE(table.InsertTableArray(table.GetNodeNameByIndex(0), "table_array2", true)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +table_array2 = [{}] +[[table_array.table_array1]])toml"); + + // Insert an inline table array behind -> this will have to be printed before the standard table array + EXPECT_TRUE(table.InsertTableArray("", "table_array3", true)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +table_array2 = [{}] +table_array3 = [{}] +[[table_array.table_array1]])toml"); + + // Insert a standard table array in front -> this will have to be printed behind the inline table array + EXPECT_TRUE(table.InsertTableArray(table.GetNodeNameByIndex(0), "table_array4", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +table_array2 = [{}] +table_array3 = [{}] +[[table_array.table_array4]] +[[table_array.table_array1]])toml"); + + // Add an additional table array entry for table array #4 + EXPECT_TRUE(table.InsertTableArray("", "table_array4", false)); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array]] +table_array2 = [{}] +table_array3 = [{}] +[[table_array.table_array4]] +[[table_array.table_array1]] +[[table_array.table_array4]])toml"); +} + +TEST(InsertNode, InsertValuesRootAsTOML) +{ + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + EXPECT_EQ(root.InsertTOML("", R"toml(# This is the first value +value_int = 10)toml", true), 1); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(# This is the first value +value_int = 10)toml"); + EXPECT_EQ(root.InsertTOML("", R"toml(value_str = "abc" # This is the second value + +# Comment in between + +# And the third value +value_float = 123.456 + +# Some +# Final +# Words :-) +)toml", true), 1); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(# This is the first value +value_int = 10 +value_str = "abc" # This is the second value + +# Comment in between + +# And the third value +value_float = 123.456 + +# Some +# Final +# Words :-) +)toml"); +} + +TEST(InsertNode, InsertTableRootAsTOML) +{ + // Insert a standard table + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + EXPECT_EQ(root.InsertTOML("", R"toml([table1] # This is table 1 +a = 10 +b = 20.30)toml", true), 1); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([table1] # This is table 1 +a = 10 +b = 20.30)toml"); + + // Insert an inline table before + EXPECT_EQ(root.InsertTOML(root.GetNodeNameByIndex(0), R"toml(# This is table 2 +table2 = {c = 40, d = "50"})toml", true), 1); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(# This is table 2 +table2 = {c = 40, d = "50"} +[table1] # This is table 1 +a = 10 +b = 20.30)toml"); + + // Insert an inline table behind -> this will have to be printed before the standard table + EXPECT_EQ(root.InsertTOML("", R"toml(table3 = {e = '60'} # This is table 3 +)toml", true), 1); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(# This is table 2 +table2 = {c = 40, d = "50"} +table3 = {e = '60'} # This is table 3 +[table1] # This is table 1 +a = 10 +b = 20.30)toml"); + + // Insert a standard table in front -> this will have to be printed behind the inline table + EXPECT_EQ(root.InsertTOML(root.GetNodeNameByIndex(0), R"toml( + +# And this is table 4 +[table4] +f = 70 +g = 80.90)toml", true), 1); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(# This is table 2 +table2 = {c = 40, d = "50"} +table3 = {e = '60'} # This is table 3 + + +# And this is table 4 +[table4] +f = 70 +g = 80.90 +[table1] # This is table 1 +a = 10 +b = 20.30)toml"); +} + +TEST(InsertNode, InsertArrayRootAsTOML) +{ + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + EXPECT_EQ(root.InsertTOML("", R"toml(value_array1 = [10, 20, 30] # This is array 1)toml", true), + 1); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(value_array1 = [10, 20, 30] # This is array 1)toml"); + EXPECT_EQ(root.InsertTOML(root.GetNodeNameByIndex(0), R"toml(# This is array 2 +value_array2 = [ + 40, # This is an integer value + 50.60, # This is a float value + "70", # This is a string value + ])toml", true), 1); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(# This is array 2 +value_array2 = [ + 40, # This is an integer value + 50.60, # This is a float value + "70", # This is a string value + ] +value_array1 = [10, 20, 30] # This is array 1)toml"); + EXPECT_EQ(root.InsertTOML(root.GetNodeNameByIndex(1), R"toml(# And finally array 3 +value_array3 = [])toml", true), 1); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(# This is array 2 +value_array2 = [ + 40, # This is an integer value + 50.60, # This is a float value + "70", # This is a string value + ] +# And finally array 3 +value_array3 = [] +value_array1 = [10, 20, 30] # This is array 1)toml"); +} + +TEST(InsertNode, InsertTableArrayRootAsTOML) +{ + // Insert a standard table array + toml_parser::CParser parser; + sdv::toml::CNodeCollection root(&parser.Root()); + EXPECT_EQ(root.InsertTOML("", R"toml([[table_array1]] +a = 10 +b = 20)toml", true), 1); + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml([[table_array1]] +a = 10 +b = 20)toml"); + + // Insert an inline table array before + EXPECT_EQ(root.InsertTOML(root.GetNodeNameByIndex(0), R"toml(table_array2 = [{c = 30}, {d = 40}])toml", true), 1); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(table_array2 = [{c = 30}, {d = 40}] +[[table_array1]] +a = 10 +b = 20)toml"); + + // Insert an inline table array behind -> this will have to be printed before the standard table array + EXPECT_EQ(root.InsertTOML("", R"toml(table_array3 = +[ + { e = 50 } +])toml", true), 1); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(table_array2 = [{c = 30}, {d = 40}] +table_array3 = +[ + { e = 50 } +] +[[table_array1]] +a = 10 +b = 20)toml"); + + // Insert a standard table array in front -> this will have to be printed behind the inline table array + EXPECT_EQ(root.InsertTOML(root.GetNodeNameByIndex(0), R"toml([[table_array4]] +f = 60 +g = 70)toml", true), 1); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(table_array2 = [{c = 30}, {d = 40}] +table_array3 = +[ + { e = 50 } +] +[[table_array4]] +f = 60 +g = 70 +[[table_array1]] +a = 10 +b = 20)toml"); + + // Add an additional table array entry for table array #4 (even provided as inline, must be added as standard). + EXPECT_EQ(root.InsertTOML("", R"toml(table_array4 = [{h = 80}])toml", true), 1); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(table_array2 = [{c = 30}, {d = 40}] +table_array3 = +[ + { e = 50 } +] +[[table_array4]] +f = 60 +g = 70 +[[table_array1]] +a = 10 +b = 20 +[[table_array4]] +h = 80)toml"); + + // Add an additional table array entry for table array #4 (even provided as inline, must be added as standard). + EXPECT_EQ(root.InsertTOML("", R"toml([[table_array3]] +i = 90 +[[table_array3]] +j = 100 +)toml", true), 1); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(root.InsertTOML(root.GetNodeNameByIndex(0), R"toml([[table_array3]] +k = 110)toml", true), 1); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(table_array2 = [{c = 30}, {d = 40}] +table_array3 = +[{k = 110}, + { e = 50 }, +{i = 90}, {j = 100}] +[[table_array4]] +f = 60 +g = 70 +[[table_array1]] +a = 10 +b = 20 +[[table_array4]] +h = 80)toml"); + + root.AutomaticFormat(true); + ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, R"toml(table_array2 = [{c = 30}, {d = 40}] +table_array3 = [{k = 110}, {e = 50}, {i = 90}, {j = 100}] +[[table_array4]] +f = 60 +g = 70 +[[table_array1]] +a = 10 +b = 20 +[[table_array4]] +h = 80)toml"); +} + +TEST(InsertNode, DISABLED_TestInsertValueInStandardTableAsTOML) +{ + //// Insert a standard table + //toml_parser::CParser parser; + //sdv::toml::INodeCollectionInsert* pStandardTable = sdv::TInterfaceAccessPtr(root.InsertTable( + // sdv::toml::npos, "standard_table", false)). + // GetInterface(); + //ASSERT_NE(pStandardTable, nullptr); + //std::string ssTOML = parser.GenerateTOML(); + //EXPECT_EQ(ssTOML, R"toml([standard_table])toml"); +} + +TEST(InsertNode, DISABLED_TestInsertTableInStandardTableAsTOML) +{ + //// Insert a standard table + //toml_parser::CParser parser; + //sdv::toml::INodeCollectionInsert* pStandardTable = sdv::TInterfaceAccessPtr(root.InsertTable( + // sdv::toml::npos, "standard_table", false)). + // GetInterface(); + //ASSERT_NE(pStandardTable, nullptr); + //std::string ssTOML = parser.GenerateTOML(); + //EXPECT_EQ(ssTOML, R"toml([standard_table])toml"); +} + +TEST(InsertNode, DISABLED_TestInsertArrayInStandardTableAsTOML) +{ + //// Insert a standard table + //toml_parser::CParser parser; + //sdv::toml::INodeCollectionInsert* pStandardTable = sdv::TInterfaceAccessPtr(root.InsertTable( + // sdv::toml::npos, "standard_table", false)). + // GetInterface(); + //ASSERT_NE(pStandardTable, nullptr); + //std::string ssTOML = parser.GenerateTOML(); + //EXPECT_EQ(ssTOML, R"toml([standard_table])toml"); +} + +TEST(InsertNode, DISABLED_TestInsertTableArrayInStandardTableAsTOML) +{ + //// Insert a standard table + //toml_parser::CParser parser; + //sdv::toml::INodeCollectionInsert* pStandardTable = sdv::TInterfaceAccessPtr(root.InsertTable( + // sdv::toml::npos, "standard_table", false)). + // GetInterface(); + //ASSERT_NE(pStandardTable, nullptr); + //std::string ssTOML = parser.GenerateTOML(); + //EXPECT_EQ(ssTOML, R"toml([standard_table])toml"); +} + +TEST(InsertNode, DISABLED_TestInsertValueInInlineTableAsTOML) +{} + +TEST(InsertNode, DISABLED_TestInsertTableInInlineTableAsTOML) +{} + +TEST(InsertNode, DISABLED_TestInsertArrayInInlineTableAsTOML) +{} + +TEST(InsertNode, DISABLED_TestInsertTableArrayInInlineTableAsTOML) +{} + +TEST(InsertNode, DISABLED_TestInsertValueInArrayAsTOML) +{} + +TEST(InsertNode, DISABLED_TestInsertTableInArrayAsTOML) +{} + +TEST(InsertNode, DISABLED_TestInsertArrayInArrayAsTOML) +{} + +TEST(InsertNode, DISABLED_TestInsertTableArrayInArrayAsTOML) +{} + +TEST(InsertNode, DISABLED_TestInsertValueInTableArrayAsTOML) +{} + +TEST(InsertNode, DISABLED_TestInsertTableInTableArrayAsTOML) +{} + +TEST(InsertNode, DISABLED_TestInsertArrayInTableArrayAsTOML) +{} + +TEST(InsertNode, DISABLED_TestInsertTableArrayInTableArrayAsTOML) +{} + +TEST(InsertNode, DISABLED_TestInsertMixed) +{} + +TEST(InsertNode, DISABLED_TestInsertMixedWithDelete) +{} diff --git a/tests/unit_tests/toml_parser/lexer_tests.cpp b/tests/unit_tests/toml_parser/lexer_tests.cpp index 1d5c85d..347dc68 100644 --- a/tests/unit_tests/toml_parser/lexer_tests.cpp +++ b/tests/unit_tests/toml_parser/lexer_tests.cpp @@ -15,6 +15,7 @@ #include #include #include +#include "../../../global/localmemmgr.h" #include "../../../sdv_services/core/toml_parser/lexer_toml.h" #include "../../../sdv_services/core/toml_parser/exception.h" @@ -114,7 +115,7 @@ * + Exceptions thrown by the CharacterReaderare caught and result in a token_terminated-Token and no further lexing will be done */ -TEST(TOMLLexerTest, Keys) +TEST(Lexer, Keys) { using namespace std::string_literals; // U+1F92B is the Finger-On-Lips Shushing emoji with UTF-8 byte representation 0xF09FA4AB @@ -169,7 +170,7 @@ TEST(TOMLLexerTest, Keys) EXPECT_EQ("1234", key8.StringValue()); } -TEST(TOMLLexerTest, SyntaxToken_NewLine) +TEST(Lexer, SyntaxToken_NewLine) { using namespace std::string_literals; toml_parser::CLexer lexer(R"( @@ -187,7 +188,7 @@ TEST(TOMLLexerTest, SyntaxToken_NewLine) EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_new_line, newLine2.Category()); } -TEST(TOMLLexerTest, SyntaxToken_Bracket) +TEST(Lexer, SyntaxToken_Bracket) { using namespace std::string_literals; toml_parser::CLexer lexer(R"( @@ -240,7 +241,7 @@ TEST(TOMLLexerTest, SyntaxToken_Bracket) EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_table_array_close, tableArrayBracketClose.Category()); } -TEST(TOMLLexerTest, SyntaxToken_Assignment) +TEST(Lexer, SyntaxToken_Assignment) { using namespace std::string_literals; toml_parser::CLexer lexerNormal(R"( @@ -279,7 +280,7 @@ TEST(TOMLLexerTest, SyntaxToken_Assignment) EXPECT_EQ(rValueNoSpace.StringValue(), "value"); } -TEST(TOMLLexerTest, Comments) +TEST(Lexer, Comments) { using namespace std::string_literals; toml_parser::CLexer lexerNormal(R"( @@ -414,7 +415,7 @@ TEST(TOMLLexerTest, Comments) EXPECT_EQ(toml_parser::ETokenCategory::token_comment, rNoSpaceTableClose.Next().Category()); } -TEST(TOMLLexerTest, SyntaxToken_Dot) +TEST(Lexer, SyntaxToken_Dot) { using namespace std::string_literals; toml_parser::CLexer lexer(R"( @@ -451,7 +452,7 @@ TEST(TOMLLexerTest, SyntaxToken_Dot) EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_dot, dot6.Category()); } -TEST(TOMLLexerTest, SyntaxToken_Braces) +TEST(Lexer, SyntaxToken_Braces) { using namespace std::string_literals; toml_parser::CLexer lexer(R"( @@ -511,7 +512,7 @@ TEST(TOMLLexerTest, SyntaxToken_Braces) EXPECT_EQ(toml_parser::ETokenCategory::token_key, emptyTable.Category()); } -TEST(TOMLLexerTest, SyntaxToken_ArrayTable) +TEST(Lexer, SyntaxToken_ArrayTable) { using namespace std::string_literals; toml_parser::CLexer lexer(R"( @@ -588,7 +589,7 @@ TEST(TOMLLexerTest, SyntaxToken_ArrayTable) EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_table_array_close, tableArrayClose5.Category()); } -TEST(TOMLLexerTest, Datatype_Integer) +TEST(Lexer, Datatype_Integer) { using namespace std::string_literals; toml_parser::CLexer lexer(R"( @@ -758,7 +759,7 @@ TEST(TOMLLexerTest, Datatype_Integer) EXPECT_EQ(toml_parser::ETokenCategory::token_error, intErr10.Category()); } -TEST(TOMLLexerTest, Datatype_Float) +TEST(Lexer, Datatype_Float) { using namespace std::string_literals; toml_parser::CLexer lexer(R"( @@ -945,7 +946,7 @@ TEST(TOMLLexerTest, Datatype_Float) EXPECT_EQ(toml_parser::ETokenCategory::token_error, errFloat28.Category()); } -TEST(TOMLLexerTest, Datatype_Boolean) +TEST(Lexer, Datatype_Boolean) { using namespace std::string_literals; toml_parser::CLexer lexer(R"( @@ -966,7 +967,7 @@ TEST(TOMLLexerTest, Datatype_Boolean) EXPECT_FALSE(falseToken.BooleanValue()); } -TEST(TOMLLexerTest, Datatype_String_BasicString) +TEST(Lexer, Datatype_String_BasicString) { using namespace std::string_literals; toml_parser::CLexer lexer(R"( @@ -1045,7 +1046,7 @@ TEST(TOMLLexerTest, Datatype_String_BasicString) EXPECT_EQ("Musical eighth note: 𝅘𝅥𝅮", string_esc9.StringValue()); } -TEST(TOMLLexerTest, Datatype_String_BasicStringMultiline) +TEST(Lexer, Datatype_String_BasicStringMultiline) { using namespace std::string_literals; toml_parser::CLexer lexer(R"( @@ -1170,7 +1171,7 @@ multiline string""" EXPECT_EQ("Musical eighth note: 𝅘𝅥𝅮 multiline", string_esc9.StringValue()); } -TEST(TOMLLexerTest, Datatype_String_LiteralString) +TEST(Lexer, Datatype_String_LiteralString) { using namespace std::string_literals; toml_parser::CLexer lexer(R"( @@ -1208,7 +1209,7 @@ TEST(TOMLLexerTest, Datatype_String_LiteralString) EXPECT_EQ("<\\i\\c*\\s*>", regex.StringValue()); } -TEST(TOMLLexerTest, Datatype_String_LiteralStringMultiline) +TEST(Lexer, Datatype_String_LiteralStringMultiline) { using namespace std::string_literals; toml_parser::CLexer lexer(R"( @@ -1257,27 +1258,27 @@ str = ''''That,' she said, 'is still pointless.'''' EXPECT_EQ("'That,' she said, 'is still pointless.'", str.StringValue()); } -// TEST(TOMLLexerTest, Datatype_OffsetDateTime) +// TEST(Lexer, Datatype_OffsetDateTime) // { // ASSERT_TRUE(false); // } -// TEST(TOMLLexerTest, Datatype_DateTime) +// TEST(Lexer, Datatype_DateTime) // { // ASSERT_TRUE(false); // } -// TEST(TOMLLexerTest, Datatype_LocalDate) +// TEST(Lexer, Datatype_LocalDate) // { // ASSERT_TRUE(false); // } -// TEST(TOMLLexerTest, Datatype_LocalTime) +// TEST(Lexer, Datatype_LocalTime) // { // ASSERT_TRUE(false); // } -TEST(TOMLLexerTest, Invalid_Key) +TEST(Lexer, Invalid_Key) { using namespace std::string_literals; toml_parser::CLexer lexer(R"( @@ -1306,7 +1307,7 @@ TEST(TOMLLexerTest, Invalid_Key) EXPECT_EQ(toml_parser::ETokenCategory::token_error, invQuotedKey3.Category()); } -TEST(TOMLLexerTest, Invalid_String) +TEST(Lexer, Invalid_String) { using namespace std::string_literals; toml_parser::CLexer lexer1(R"(key1 = "invalid escape sequence \h)"s); @@ -1332,7 +1333,7 @@ TEST(TOMLLexerTest, Invalid_String) EXPECT_EQ(toml_parser::ETokenCategory::token_error, invString4.Category()); } -TEST(TOMLLexerTest, Invalid_Integer) +TEST(Lexer, Invalid_Integer) { using namespace std::string_literals; toml_parser::CLexer lexer(R"( @@ -1406,7 +1407,7 @@ TEST(TOMLLexerTest, Invalid_Integer) EXPECT_EQ(toml_parser::ETokenCategory::token_error, invInteger15.Category()); } -TEST(TOMLLexerTest, Invalid_Float) +TEST(Lexer, Invalid_Float) { using namespace std::string_literals; toml_parser::CLexer lexer(R"( @@ -1440,7 +1441,7 @@ TEST(TOMLLexerTest, Invalid_Float) EXPECT_EQ(toml_parser::ETokenCategory::token_error, invFloat5.Category()); } -TEST(TOMLLexerTest, Invalid_Boolean) +TEST(Lexer, Invalid_Boolean) { using namespace std::string_literals; toml_parser::CLexer lexer(R"( @@ -1459,27 +1460,27 @@ TEST(TOMLLexerTest, Invalid_Boolean) EXPECT_EQ(toml_parser::ETokenCategory::token_error, invBool2.Category()); } -// TEST(TOMLLexerTest, Invalid_OffsetDateTime) +// TEST(Lexer, Invalid_OffsetDateTime) // { // ASSERT_TRUE(false); // } -// TEST(TOMLLexerTest, Invalid_LocalDateTime) +// TEST(Lexer, Invalid_LocalDateTime) // { // ASSERT_TRUE(false); // } -// TEST(TOMLLexerTest, Invalid_LocalDate) +// TEST(Lexer, Invalid_LocalDate) // { // ASSERT_TRUE(false); // } -// TEST(TOMLLexerTest, Invalid_LocalTime) +// TEST(Lexer, Invalid_LocalTime) // { // ASSERT_TRUE(false); // } -TEST(TOMLLexerTest, Peek_NoAdvance) +TEST(Lexer, Peek_NoAdvance) { using namespace std::string_literals; toml_parser::CLexer lexer(R"( @@ -1500,7 +1501,7 @@ TEST(TOMLLexerTest, Peek_NoAdvance) EXPECT_EQ(toml_parser::ETokenCategory::token_key, lexer.Peek(1).Category()); } -TEST(TOMLLexerTest, Consume_Advance) +TEST(Lexer, Consume_Advance) { using namespace std::string_literals; toml_parser::CLexer lexer(R"( @@ -1525,7 +1526,7 @@ TEST(TOMLLexerTest, Consume_Advance) EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_assignment, ptr.Category()); } -TEST(TOMLLexerTest, PeekConsume_BoundsCheck) +TEST(Lexer, PeekConsume_BoundsCheck) { using namespace std::string_literals; toml_parser::CLexer lexer(R"( @@ -1544,7 +1545,7 @@ TEST(TOMLLexerTest, PeekConsume_BoundsCheck) EXPECT_FALSE(lexer.Consume(0)); } -TEST(TOMLLexerTest, PeekConsume_EmptyInput) +TEST(Lexer, PeekConsume_EmptyInput) { using namespace std::string_literals; toml_parser::CLexer lexer(R"()"s); @@ -1555,7 +1556,7 @@ TEST(TOMLLexerTest, PeekConsume_EmptyInput) EXPECT_FALSE(lexer.Consume(1)); } -TEST(TOMLLexerTest, ExceptionHandling) +TEST(Lexer, ExceptionHandling) { using namespace std::string_literals; toml_parser::CLexer lexer; @@ -1577,7 +1578,7 @@ TEST(TOMLLexerTest, ExceptionHandling) EXPECT_FALSE(lexer.Consume(0)); } -TEST(TOMLLexerTest, DISABLED_RegenerateTOML) +TEST(Lexer, DISABLED_RegenerateTOML) { using namespace std::string_literals; std::string ssOrginal = R"( diff --git a/tests/unit_tests/toml_parser/main.cpp b/tests/unit_tests/toml_parser/main.cpp index 91e99c3..ecec02e 100644 --- a/tests/unit_tests/toml_parser/main.cpp +++ b/tests/unit_tests/toml_parser/main.cpp @@ -14,6 +14,7 @@ #include #include "../../../global/process_watchdog.h" +#include "../../../global/localmemmgr.h" #include "../../../sdv_services/core/toml_parser/character_reader_utf_8.cpp" #include "../../../sdv_services/core/toml_parser/lexer_toml.cpp" #include "../../../sdv_services/core/toml_parser/lexer_toml_token.cpp" @@ -21,6 +22,8 @@ #include "../../../sdv_services/core/toml_parser/parser_node_toml.cpp" #include "../../../sdv_services/core/toml_parser/miscellaneous.cpp" #include "../../../sdv_services/core/toml_parser/code_snippet.cpp" +#include "../../../sdv_services/core/toml_parser/parser_node_indexer.cpp" + #if defined(_WIN32) && defined(_UNICODE) extern "C" int wmain(int argc, wchar_t* argv[]) @@ -30,6 +33,7 @@ extern "C" int main(int argc, char* argv[]) { CProcessWatchdog watchdog; + CLocalMemMgr memmgr; testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } diff --git a/tests/unit_tests/toml_parser/make_inline_standard.cpp b/tests/unit_tests/toml_parser/make_inline_standard.cpp new file mode 100644 index 0000000..30386be --- /dev/null +++ b/tests/unit_tests/toml_parser/make_inline_standard.cpp @@ -0,0 +1,289 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial implementation + ********************************************************************************/ + +#include +#include +#include +#include "../../../global/localmemmgr.h" +#include "../../../sdv_services/core/toml_parser/exception.h" +#include "../../../sdv_services/core/toml_parser/miscellaneous.h" +#include "../../../sdv_services/core/toml_parser/parser_toml.h" +#include + +TEST(MakeInline, Value) +{ + std::string ssTomlIn = R"toml(value1 = 1)toml"; + std::string ssTomlOut = R"toml(value1 = 1)toml"; + + toml_parser::CParser parser(ssTomlIn); + EXPECT_TRUE(parser.Root().CanMakeInline()); + EXPECT_TRUE(parser.Root().MakeInline()); + std::string ssOut = parser.Root().GetTOML(); + EXPECT_EQ(ssTomlOut, ssOut); +} + +TEST(MakeStandard, Value) +{ + std::string ssTomlIn = R"toml(value1 = 1)toml"; + std::string ssTomlOut = R"toml(value1 = 1)toml"; + + toml_parser::CParser parser(ssTomlIn); + EXPECT_FALSE(parser.Root().CanMakeStandard()); + EXPECT_FALSE(parser.Root().MakeStandard(true)); + std::string ssOut = parser.Root().GetTOML(); + EXPECT_EQ(ssTomlOut, ssOut); +} + +TEST(MakeInline, Array) +{ + std::string ssTomlIn = R"toml(value1 = ["abc", "def", "ghi"])toml"; + std::string ssTomlOut = R"toml(value1 = ["abc", "def", "ghi"])toml"; + + toml_parser::CParser parser(ssTomlIn); + EXPECT_TRUE(parser.Root().CanMakeInline()); + EXPECT_TRUE(parser.Root().MakeInline()); + std::string ssOut = parser.Root().GetTOML(); + EXPECT_EQ(ssTomlOut, ssOut); +} + +TEST(MakeStandard, Array) +{ + std::string ssTomlIn = R"toml(value1 = ["abc", "def", "ghi"])toml"; + std::string ssTomlOut = R"toml(value1 = ["abc", "def", "ghi"])toml"; + + toml_parser::CParser parser(ssTomlIn); + EXPECT_FALSE(parser.Root().CanMakeStandard()); + EXPECT_FALSE(parser.Root().MakeStandard(true)); + std::string ssOut = parser.Root().GetTOML(); + EXPECT_EQ(ssTomlOut, ssOut); +} + +TEST(MakeInline, StandardTable) +{ + std::string ssTomlIn = R"toml([table1] +value = 1)toml"; + std::string ssTomlOut = R"toml(table1 = {value = 1})toml"; + + toml_parser::CParser parser(ssTomlIn); + EXPECT_TRUE(parser.Root().CanMakeInline()); + EXPECT_TRUE(parser.Root().MakeInline()); + std::string ssOut = parser.Root().GetTOML(); + EXPECT_EQ(ssTomlOut, ssOut); +} + +TEST(MakeStandard, StandardTable) +{ + std::string ssTomlIn = R"toml([table1] +value = 1)toml"; + std::string ssTomlOut = R"toml([table1] +value = 1)toml"; + + toml_parser::CParser parser(ssTomlIn); + EXPECT_TRUE(parser.Root().CanMakeStandard()); + EXPECT_TRUE(parser.Root().MakeStandard(true)); + std::string ssOut = parser.Root().GetTOML(); + EXPECT_EQ(ssTomlOut, ssOut); +} + +TEST(MakeInline, InlineTable) +{ + std::string ssTomlIn = R"toml(table1 = {value = 1})toml"; + std::string ssTomlOut = R"toml(table1 = {value = 1})toml"; + + toml_parser::CParser parser(ssTomlIn); + EXPECT_TRUE(parser.Root().CanMakeInline()); + EXPECT_TRUE(parser.Root().MakeInline()); + std::string ssOut = parser.Root().GetTOML(); + EXPECT_EQ(ssTomlOut, ssOut); +} + +TEST(MakeStandard, InlineTable) +{ + std::string ssTomlIn = R"toml(table1 = {value = 1})toml"; + std::string ssTomlOut = R"toml([table1] +value = 1)toml"; + + toml_parser::CParser parser(ssTomlIn); + EXPECT_TRUE(parser.Root().CanMakeStandard()); + EXPECT_TRUE(parser.Root().MakeStandard(true)); + std::string ssOut = parser.Root().GetTOML(); + EXPECT_EQ(ssTomlOut, ssOut); +} + +TEST(MakeInline, StandardTableArray) +{ + std::string ssTomlIn = R"toml([[table_array1]] +value = 1)toml"; + std::string ssTomlOut = R"toml(table_array1 = [{value = 1}])toml"; + + toml_parser::CParser parser(ssTomlIn); + EXPECT_TRUE(parser.Root().CanMakeInline()); + EXPECT_TRUE(parser.Root().MakeInline()); + std::string ssOut = parser.Root().GetTOML(); + EXPECT_EQ(ssTomlOut, ssOut); +} + +TEST(MakeStandard, StandardTableArray) +{ + std::string ssTomlIn = R"toml([[table_array1]] +value = 1)toml"; + std::string ssTomlOut = R"toml([[table_array1]] +value = 1)toml"; + + toml_parser::CParser parser(ssTomlIn); + EXPECT_TRUE(parser.Root().CanMakeStandard()); + EXPECT_TRUE(parser.Root().MakeStandard(true)); + std::string ssOut = parser.Root().GetTOML(); + EXPECT_EQ(ssTomlOut, ssOut); +} + +TEST(MakeInline, InlineTableArray) +{ + std::string ssTomlIn = R"toml(table_array1 = [{value = 1}])toml"; + std::string ssTomlOut = R"toml(table_array1 = [{value = 1}])toml"; + + toml_parser::CParser parser(ssTomlIn); + EXPECT_TRUE(parser.Root().CanMakeInline()); + EXPECT_TRUE(parser.Root().MakeInline()); + std::string ssOut = parser.Root().GetTOML(); + EXPECT_EQ(ssTomlOut, ssOut); +} + +TEST(MakeStandard, InlineTableArray) +{ + std::string ssTomlIn = R"toml(table_array1 = [{value = 1}])toml"; + std::string ssTomlOut = R"toml([[table_array1]] +value = 1)toml"; + + toml_parser::CParser parser(ssTomlIn); + EXPECT_TRUE(parser.Root().CanMakeStandard()); + EXPECT_TRUE(parser.Root().MakeStandard(true)); + std::string ssOut = parser.Root().GetTOML(); + EXPECT_EQ(ssTomlOut, ssOut); +} + +TEST(MakeInline, StandardTableImplicit) +{ + std::string ssTomlIn = R"toml([root_table.table1] +value = 1)toml"; + std::string ssTomlOut = R"toml(root_table.table1 = {value = 1})toml"; + + toml_parser::CParser parser(ssTomlIn); + EXPECT_TRUE(parser.Root().CanMakeInline()); + EXPECT_TRUE(parser.Root().MakeInline()); + std::string ssOut = parser.Root().GetTOML(); + EXPECT_EQ(ssTomlOut, ssOut); +} + +TEST(MakeStandard, StandardTableImplicit) +{ + std::string ssTomlIn = R"toml([root_table.table1] +value = 1)toml"; + std::string ssTomlOut = R"toml([root_table.table1] +value = 1)toml"; + + toml_parser::CParser parser(ssTomlIn); + EXPECT_TRUE(parser.Root().CanMakeStandard()); + EXPECT_TRUE(parser.Root().MakeStandard(true)); + std::string ssOut = parser.Root().GetTOML(); + EXPECT_EQ(ssTomlOut, ssOut); +} + +TEST(MakeInline, InlineTableImplicit) +{ + std::string ssTomlIn = R"toml(root_table.table1 = {value = 1})toml"; + std::string ssTomlOut = R"toml(root_table.table1 = {value = 1})toml"; + + toml_parser::CParser parser(ssTomlIn); + EXPECT_TRUE(parser.Root().CanMakeInline()); + EXPECT_TRUE(parser.Root().MakeInline()); + std::string ssOut = parser.Root().GetTOML(); + EXPECT_EQ(ssTomlOut, ssOut); +} + +TEST(MakeStandard, InlineTableImplicit) +{ + std::string ssTomlIn = R"toml(root_table.table1 = {value = 1})toml"; + std::string ssTomlOut = R"toml([root_table.table1] +value = 1)toml"; + + toml_parser::CParser parser(ssTomlIn); + EXPECT_TRUE(parser.Root().CanMakeStandard()); + EXPECT_TRUE(parser.Root().MakeStandard(true)); + std::string ssOut = parser.Root().GetTOML(); + EXPECT_EQ(ssTomlOut, ssOut); +} + +TEST(MakeInline, StandardTableNested) +{ + std::string ssTomlIn = R"toml([table1] +value1 = 1 +[table1.table2] +value2 = 2 +[table1.table3.table4] +value3 = 3 +value4 = 4)toml"; + std::string ssTomlOut = R"toml(table1 = {value1 = 1, table2 = {value2 = 2}, table3.table4 = {value3 = 3, value4 = 4}})toml"; + + toml_parser::CParser parser(ssTomlIn); + EXPECT_TRUE(parser.Root().CanMakeInline()); + EXPECT_TRUE(parser.Root().MakeInline()); + std::string ssOut = parser.Root().GetTOML(); + EXPECT_EQ(ssTomlOut, ssOut); +} + +TEST(MakeStandard, StandardTableNested) +{ + std::string ssTomlIn = R"toml([table1] +value = 1 +[table1.table2] +value = 2)toml"; + std::string ssTomlOut = R"toml([table1] +value = 1 +[table1.table2] +value = 2)toml"; + + toml_parser::CParser parser(ssTomlIn); + EXPECT_TRUE(parser.Root().CanMakeStandard()); + EXPECT_TRUE(parser.Root().MakeStandard(true)); + std::string ssOut = parser.Root().GetTOML(); + EXPECT_EQ(ssTomlOut, ssOut); +} + +TEST(MakeInline, InlineTableNested) +{ + std::string ssTomlIn = R"toml(table1 = {value = 1, table2 = {value = 2}})toml"; + std::string ssTomlOut = R"toml(table1 = {value = 1, table2 = {value = 2}})toml"; + + toml_parser::CParser parser(ssTomlIn); + EXPECT_TRUE(parser.Root().CanMakeInline()); + EXPECT_TRUE(parser.Root().MakeInline()); + std::string ssOut = parser.Root().GetTOML(); + EXPECT_EQ(ssTomlOut, ssOut); +} + +TEST(MakeStandard, InlineTableNested) +{ + std::string ssTomlIn = R"toml(table1 = {value = 1, table2 = {value = 2}})toml"; + std::string ssTomlOut = R"toml([table1] +value = 1 +[table1.table2] +value = 2)toml"; + + toml_parser::CParser parser(ssTomlIn); + EXPECT_TRUE(parser.Root().CanMakeStandard()); + EXPECT_TRUE(parser.Root().MakeStandard(true)); + std::string ssOut = parser.Root().GetTOML(); + EXPECT_EQ(ssTomlOut, ssOut); +} + diff --git a/tests/unit_tests/toml_parser/miscellaneous_tests.cpp b/tests/unit_tests/toml_parser/miscellaneous.cpp similarity index 96% rename from tests/unit_tests/toml_parser/miscellaneous_tests.cpp rename to tests/unit_tests/toml_parser/miscellaneous.cpp index e1d85c8..52868a5 100644 --- a/tests/unit_tests/toml_parser/miscellaneous_tests.cpp +++ b/tests/unit_tests/toml_parser/miscellaneous.cpp @@ -15,11 +15,16 @@ #include #include #include - +#include "../../../global/localmemmgr.h" #include "../../../sdv_services/core/toml_parser/exception.h" #include "../../../sdv_services/core/toml_parser/miscellaneous.h" -TEST(TOMLMiscellaneousTests, Hex2Dec) +// Test TODO: +// Shift nodes up and down within one container +// Remove formatting from node +// Compare TOMLs + +TEST(Miscellaneous, Hex2Dec) { // No string std::string ssEmpty; @@ -47,7 +52,7 @@ TEST(TOMLMiscellaneousTests, Hex2Dec) EXPECT_EQ(toml_parser::HexadecimalToDecimal("10xyz"), 16u); } -TEST(TOMLMiscellaneousTests, Dec2Dec) +TEST(Miscellaneous, Dec2Dec) { // No string std::string ssEmpty; @@ -75,7 +80,7 @@ TEST(TOMLMiscellaneousTests, Dec2Dec) EXPECT_EQ(toml_parser::DecimalToDecimal("10xyz"), 10u); } -TEST(TOMLMiscellaneousTests, Oct2Dec) +TEST(Miscellaneous, Oct2Dec) { // No string std::string ssEmpty; @@ -103,7 +108,7 @@ TEST(TOMLMiscellaneousTests, Oct2Dec) EXPECT_EQ(toml_parser::OctalToDecimal("12xyz"), 10u); } -TEST(TOMLMiscellaneousTests, Bin2Dec) +TEST(Miscellaneous, Bin2Dec) { // No string std::string ssEmpty; @@ -131,7 +136,7 @@ TEST(TOMLMiscellaneousTests, Bin2Dec) EXPECT_EQ(toml_parser::BinaryToDecimal("1010yz"), 10u); } -TEST(TOMLMiscellaneousTests, UnicodeCharacter) +TEST(Miscellaneous, UnicodeCharacter) { // No string std::string ssEmpty; @@ -147,7 +152,7 @@ TEST(TOMLMiscellaneousTests, UnicodeCharacter) EXPECT_EQ(toml_parser::EscapedUnicodeCharacterToUTF8("0001F600"), u8"\U0001F600"); } -TEST(TOMLMiscellaneousTests, SplitKeyStringEmpty) +TEST(Miscellaneous, SplitKeyStringEmpty) { std::string ssKeyEmpty; auto prSplittedKey = toml_parser::SplitNodeKey(ssKeyEmpty); @@ -155,7 +160,7 @@ TEST(TOMLMiscellaneousTests, SplitKeyStringEmpty) EXPECT_TRUE(prSplittedKey.second.empty()); } -TEST(TOMLMiscellaneousTests, SplitKeyFirstPartOnly) +TEST(Miscellaneous, SplitKeyFirstPartOnly) { std::string ssKey = "abc"; auto prSplittedKey = toml_parser::SplitNodeKey(ssKey); @@ -196,7 +201,7 @@ TEST(TOMLMiscellaneousTests, SplitKeyFirstPartOnly) EXPECT_TRUE(prSplittedKey.second.empty()); } -TEST(TOMLMiscellaneousTests, SplitStandardBareKey) +TEST(Miscellaneous, SplitStandardBareKey) { std::string ssKey = "abc.def"; auto prSplittedKey = toml_parser::SplitNodeKey(ssKey); @@ -236,7 +241,7 @@ TEST(TOMLMiscellaneousTests, SplitStandardBareKey) EXPECT_TRUE(prSplittedKey.second.empty()); } -TEST(TOMLMiscellaneousTests, SplitBareKeyWithSpace) +TEST(Miscellaneous, SplitBareKeyWithSpace) { std::string ssKey = " abc.def"; auto prSplittedKey = toml_parser::SplitNodeKey(ssKey); @@ -265,7 +270,7 @@ TEST(TOMLMiscellaneousTests, SplitBareKeyWithSpace) EXPECT_TRUE(prSplittedKey.second.empty()); } -TEST(TOMLMiscellaneousTests, SplitLiteralKey) +TEST(Miscellaneous, SplitLiteralKey) { std::string ssKey = "'abc'"; auto prSplittedKey = toml_parser::SplitNodeKey(ssKey); @@ -309,7 +314,7 @@ TEST(TOMLMiscellaneousTests, SplitLiteralKey) EXPECT_TRUE(prSplittedKey.second.empty()); } -TEST(TOMLMiscellaneousTests, SplitQuotedKey) +TEST(Miscellaneous, SplitQuotedKey) { std::string ssKey = "\"abc\""; auto prSplittedKey = toml_parser::SplitNodeKey(ssKey); @@ -348,7 +353,7 @@ TEST(TOMLMiscellaneousTests, SplitQuotedKey) EXPECT_TRUE(prSplittedKey.second.empty()); } -TEST(TOMLMiscellaneousTests, SplitEscapedQuotedKey) +TEST(Miscellaneous, SplitEscapedQuotedKey) { std::string ssKey = "\"abc\\bdef\""; auto prSplittedKey = toml_parser::SplitNodeKey(ssKey); @@ -413,7 +418,7 @@ TEST(TOMLMiscellaneousTests, SplitEscapedQuotedKey) EXPECT_TRUE(prSplittedKey.second.empty()); } -TEST(TOMLMiscellaneousTests, SplitTableKey) +TEST(Miscellaneous, SplitTableKey) { std::string ssKey = "abc.def"; auto prSplittedKey = toml_parser::SplitNodeKey(ssKey); @@ -431,7 +436,7 @@ TEST(TOMLMiscellaneousTests, SplitTableKey) EXPECT_EQ(prSplittedKey.second, "def.ghi"); } -TEST(TOMLMiscellaneousTests, SplitArrayKey) +TEST(Miscellaneous, SplitArrayKey) { std::string ssKey = "abc[1]"; auto prSplittedKey = toml_parser::SplitNodeKey(ssKey); @@ -493,7 +498,7 @@ TEST(TOMLMiscellaneousTests, SplitArrayKey) EXPECT_TRUE(prSplittedKey.second.empty()); } -TEST(TOMLMiscellaneousTests, SmartQuoteBareKeys) +TEST(Miscellaneous, SmartQuoteBareKeys) { EXPECT_EQ(toml_parser::QuoteText("abc", toml_parser::EQuoteRequest::smart_key), "abc"); EXPECT_EQ(toml_parser::QuoteText("123", toml_parser::EQuoteRequest::smart_key), "123"); @@ -502,7 +507,7 @@ TEST(TOMLMiscellaneousTests, SmartQuoteBareKeys) EXPECT_EQ(toml_parser::QuoteText("ABC-DEF", toml_parser::EQuoteRequest::smart_key), "ABC-DEF"); } -TEST(TOMLMiscellaneousTests, SmartQuoteSpecialCharsKeys) +TEST(Miscellaneous, SmartQuoteSpecialCharsKeys) { EXPECT_EQ(toml_parser::QuoteText("", toml_parser::EQuoteRequest::smart_key), "\"\""); EXPECT_EQ(toml_parser::QuoteText("abc def", toml_parser::EQuoteRequest::smart_key), "\"abc def\""); @@ -515,7 +520,7 @@ TEST(TOMLMiscellaneousTests, SmartQuoteSpecialCharsKeys) EXPECT_EQ(toml_parser::QuoteText("abc/", toml_parser::EQuoteRequest::smart_key), "\"abc/\""); } -TEST(TOMLMiscellaneousTests, SmartQuoteEscapeCharsKeys) +TEST(Miscellaneous, SmartQuoteEscapeCharsKeys) { EXPECT_EQ(toml_parser::QuoteText("abc\tdef", toml_parser::EQuoteRequest::smart_key), "\"abc\\tdef\""); EXPECT_EQ(toml_parser::QuoteText("abc\\def", toml_parser::EQuoteRequest::smart_key), "\"abc\\\\def\""); @@ -527,7 +532,7 @@ TEST(TOMLMiscellaneousTests, SmartQuoteEscapeCharsKeys) EXPECT_EQ(toml_parser::QuoteText("abc\rdef", toml_parser::EQuoteRequest::smart_key), "\"abc\\rdef\""); } -TEST(TOMLMiscellaneousTests, SmartQuoteControlCharsKeys) +TEST(Miscellaneous, SmartQuoteControlCharsKeys) { EXPECT_EQ(toml_parser::QuoteText(std::string(1, '\0'), toml_parser::EQuoteRequest::smart_key), "\"\\u0000\""); EXPECT_EQ(toml_parser::QuoteText("\u0001", toml_parser::EQuoteRequest::smart_key), "\"\\u0001\""); @@ -567,7 +572,7 @@ TEST(TOMLMiscellaneousTests, SmartQuoteControlCharsKeys) // 0080... and higher are treated as unicode character (quotation needed) } -TEST(TOMLMiscellaneousTests, SmartQuoteText) +TEST(Miscellaneous, SmartQuoteText) { EXPECT_EQ(toml_parser::QuoteText("abc", toml_parser::EQuoteRequest::smart_text), "\"abc\""); EXPECT_EQ(toml_parser::QuoteText("123", toml_parser::EQuoteRequest::smart_text), "\"123\""); @@ -576,7 +581,7 @@ TEST(TOMLMiscellaneousTests, SmartQuoteText) EXPECT_EQ(toml_parser::QuoteText("ABC-DEF", toml_parser::EQuoteRequest::smart_text), "\"ABC-DEF\""); } -TEST(TOMLMiscellaneousTests, SmartQuoteSpecialCharsText) +TEST(Miscellaneous, SmartQuoteSpecialCharsText) { EXPECT_EQ(toml_parser::QuoteText("", toml_parser::EQuoteRequest::smart_text), "\"\""); EXPECT_EQ(toml_parser::QuoteText("abc def", toml_parser::EQuoteRequest::smart_text), "\"abc def\""); @@ -589,7 +594,7 @@ TEST(TOMLMiscellaneousTests, SmartQuoteSpecialCharsText) EXPECT_EQ(toml_parser::QuoteText("abc/", toml_parser::EQuoteRequest::smart_text), "\"abc/\""); } -TEST(TOMLMiscellaneousTests, SmartQuoteEscapeCharsText) +TEST(Miscellaneous, SmartQuoteEscapeCharsText) { EXPECT_EQ(toml_parser::QuoteText("abc\tdef", toml_parser::EQuoteRequest::smart_text), "\"abc\\tdef\""); EXPECT_EQ(toml_parser::QuoteText("abc\\def", toml_parser::EQuoteRequest::smart_text), "'abc\\def'"); @@ -601,7 +606,7 @@ TEST(TOMLMiscellaneousTests, SmartQuoteEscapeCharsText) EXPECT_EQ(toml_parser::QuoteText("abc\rdef", toml_parser::EQuoteRequest::smart_text), "\"abc\\rdef\""); } -TEST(TOMLMiscellaneousTests, SmartQuoteControlCharsText) +TEST(Miscellaneous, SmartQuoteControlCharsText) { EXPECT_EQ(toml_parser::QuoteText(std::string(1, '\0'), toml_parser::EQuoteRequest::smart_text), "\"\\u0000\""); EXPECT_EQ(toml_parser::QuoteText("\u0001", toml_parser::EQuoteRequest::smart_text), "\"\\u0001\""); @@ -641,7 +646,7 @@ TEST(TOMLMiscellaneousTests, SmartQuoteControlCharsText) // 0080... and higher are treated as unicode character (quotation needed) } -TEST(TOMLMiscellaneousTests, QuotedText) +TEST(Miscellaneous, QuotedText) { EXPECT_EQ(toml_parser::QuoteText("abc", toml_parser::EQuoteRequest::quoted_text), "\"abc\""); EXPECT_EQ(toml_parser::QuoteText("123", toml_parser::EQuoteRequest::quoted_text), "\"123\""); @@ -650,7 +655,7 @@ TEST(TOMLMiscellaneousTests, QuotedText) EXPECT_EQ(toml_parser::QuoteText("ABC-DEF", toml_parser::EQuoteRequest::quoted_text), "\"ABC-DEF\""); } -TEST(TOMLMiscellaneousTests, QuotedSpecialCharsText) +TEST(Miscellaneous, QuotedSpecialCharsText) { EXPECT_EQ(toml_parser::QuoteText("", toml_parser::EQuoteRequest::quoted_text), "\"\""); EXPECT_EQ(toml_parser::QuoteText("abc def", toml_parser::EQuoteRequest::quoted_text), "\"abc def\""); @@ -663,7 +668,7 @@ TEST(TOMLMiscellaneousTests, QuotedSpecialCharsText) EXPECT_EQ(toml_parser::QuoteText("abc/", toml_parser::EQuoteRequest::quoted_text), "\"abc/\""); } -TEST(TOMLMiscellaneousTests, QuotedEscapeCharsText) +TEST(Miscellaneous, QuotedEscapeCharsText) { EXPECT_EQ(toml_parser::QuoteText("abc\tdef", toml_parser::EQuoteRequest::quoted_text), "\"abc\\tdef\""); EXPECT_EQ(toml_parser::QuoteText("abc\\def", toml_parser::EQuoteRequest::quoted_text), "\"abc\\\\def\""); @@ -675,7 +680,7 @@ TEST(TOMLMiscellaneousTests, QuotedEscapeCharsText) EXPECT_EQ(toml_parser::QuoteText("abc\rdef", toml_parser::EQuoteRequest::quoted_text), "\"abc\\rdef\""); } -TEST(TOMLMiscellaneousTests, QuotedControlCharsText) +TEST(Miscellaneous, QuotedControlCharsText) { EXPECT_EQ(toml_parser::QuoteText(std::string(1, '\0'), toml_parser::EQuoteRequest::quoted_text), "\"\\u0000\""); EXPECT_EQ(toml_parser::QuoteText("\u0001", toml_parser::EQuoteRequest::quoted_text), "\"\\u0001\""); @@ -715,7 +720,7 @@ TEST(TOMLMiscellaneousTests, QuotedControlCharsText) // 0080... and higher are treated as unicode character (quotation needed) } -TEST(TOMLMiscellaneousTests, LiteralText) +TEST(Miscellaneous, LiteralText) { EXPECT_EQ(toml_parser::QuoteText("abc", toml_parser::EQuoteRequest::literal_text), "'abc'"); EXPECT_EQ(toml_parser::QuoteText("123", toml_parser::EQuoteRequest::literal_text), "'123'"); @@ -724,7 +729,7 @@ TEST(TOMLMiscellaneousTests, LiteralText) EXPECT_EQ(toml_parser::QuoteText("ABC-DEF", toml_parser::EQuoteRequest::literal_text), "'ABC-DEF'"); } -TEST(TOMLMiscellaneousTests, LiteralSpecialCharsText) +TEST(Miscellaneous, LiteralSpecialCharsText) { EXPECT_EQ(toml_parser::QuoteText("", toml_parser::EQuoteRequest::literal_text), "''"); EXPECT_EQ(toml_parser::QuoteText("abc def", toml_parser::EQuoteRequest::literal_text), "'abc def'"); @@ -737,7 +742,7 @@ TEST(TOMLMiscellaneousTests, LiteralSpecialCharsText) EXPECT_EQ(toml_parser::QuoteText("abc/", toml_parser::EQuoteRequest::literal_text), "'abc/'"); } -TEST(TOMLMiscellaneousTests, LiteralEscapeCharsText) +TEST(Miscellaneous, LiteralEscapeCharsText) { EXPECT_EQ(toml_parser::QuoteText("abc\tdef", toml_parser::EQuoteRequest::literal_text), "\"abc\\tdef\""); // Becomes quoted EXPECT_EQ(toml_parser::QuoteText("abc\\def", toml_parser::EQuoteRequest::literal_text), "'abc\\def'"); @@ -749,7 +754,7 @@ TEST(TOMLMiscellaneousTests, LiteralEscapeCharsText) EXPECT_EQ(toml_parser::QuoteText("abc\rdef", toml_parser::EQuoteRequest::literal_text), "\"abc\\rdef\""); // Becomes quoted } -TEST(TOMLMiscellaneousTests, LiteralControlCharsText) +TEST(Miscellaneous, LiteralControlCharsText) { // All become quoted insteda of literal (due to control character). EXPECT_EQ(toml_parser::QuoteText(std::string(1, '\0'), toml_parser::EQuoteRequest::literal_text), "\"\\u0000\""); @@ -790,7 +795,7 @@ TEST(TOMLMiscellaneousTests, LiteralControlCharsText) // 0080... and higher are treated as unicode character (quotation needed) } -TEST(TOMLMiscellaneousTests, MultiLineQuotedText) +TEST(Miscellaneous, MultiLineQuotedText) { EXPECT_EQ(toml_parser::QuoteText("abc", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"abc\"\"\""); EXPECT_EQ(toml_parser::QuoteText("123", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"123\"\"\""); @@ -799,7 +804,7 @@ TEST(TOMLMiscellaneousTests, MultiLineQuotedText) EXPECT_EQ(toml_parser::QuoteText("ABC-DEF", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"ABC-DEF\"\"\""); } -TEST(TOMLMiscellaneousTests, MultiLineQuotedSpecialCharsText) +TEST(Miscellaneous, MultiLineQuotedSpecialCharsText) { EXPECT_EQ(toml_parser::QuoteText("", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\"\"\""); EXPECT_EQ(toml_parser::QuoteText("abc def", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"abc def\"\"\""); @@ -812,7 +817,7 @@ TEST(TOMLMiscellaneousTests, MultiLineQuotedSpecialCharsText) EXPECT_EQ(toml_parser::QuoteText("abc/", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"abc/\"\"\""); } -TEST(TOMLMiscellaneousTests, MultiLineQuotedEscapeCharsText) +TEST(Miscellaneous, MultiLineQuotedEscapeCharsText) { EXPECT_EQ(toml_parser::QuoteText("abc\tdef", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"abc\\tdef\"\"\""); EXPECT_EQ(toml_parser::QuoteText("abc\\def", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"abc\\\\def\"\"\""); @@ -826,7 +831,7 @@ TEST(TOMLMiscellaneousTests, MultiLineQuotedEscapeCharsText) EXPECT_EQ(toml_parser::QuoteText("abc\rdef", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"abc\rdef\"\"\""); } -TEST(TOMLMiscellaneousTests, MultiLineQuotedControlCharsText) +TEST(Miscellaneous, MultiLineQuotedControlCharsText) { EXPECT_EQ(toml_parser::QuoteText(std::string(1, '\0'), toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u0000\"\"\""); EXPECT_EQ(toml_parser::QuoteText("\u0001", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u0001\"\"\""); @@ -866,7 +871,7 @@ TEST(TOMLMiscellaneousTests, MultiLineQuotedControlCharsText) // 0080... and higher are treated as unicode character (quotation needed) } -TEST(TOMLMiscellaneousTests, MultiLineLiteralText) +TEST(Miscellaneous, MultiLineLiteralText) { EXPECT_EQ(toml_parser::QuoteText("abc", toml_parser::EQuoteRequest::multi_line_literal_text), "'''abc'''"); EXPECT_EQ(toml_parser::QuoteText("123", toml_parser::EQuoteRequest::multi_line_literal_text), "'''123'''"); @@ -875,7 +880,7 @@ TEST(TOMLMiscellaneousTests, MultiLineLiteralText) EXPECT_EQ(toml_parser::QuoteText("ABC-DEF", toml_parser::EQuoteRequest::multi_line_literal_text), "'''ABC-DEF'''"); } -TEST(TOMLMiscellaneousTests, MultiLineLiteralSpecialCharsText) +TEST(Miscellaneous, MultiLineLiteralSpecialCharsText) { EXPECT_EQ(toml_parser::QuoteText("", toml_parser::EQuoteRequest::multi_line_literal_text), "''''''"); EXPECT_EQ(toml_parser::QuoteText("abc def", toml_parser::EQuoteRequest::multi_line_literal_text), "'''abc def'''"); @@ -888,7 +893,7 @@ TEST(TOMLMiscellaneousTests, MultiLineLiteralSpecialCharsText) EXPECT_EQ(toml_parser::QuoteText("abc/", toml_parser::EQuoteRequest::multi_line_literal_text), "'''abc/'''"); } -TEST(TOMLMiscellaneousTests, MultiLineLiteralEscapeCharsText) +TEST(Miscellaneous, MultiLineLiteralEscapeCharsText) { EXPECT_EQ(toml_parser::QuoteText("abc\tdef", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"abc\\tdef\"\"\""); // Becomes quoted EXPECT_EQ(toml_parser::QuoteText("abc\\def", toml_parser::EQuoteRequest::multi_line_literal_text), "'''abc\\def'''"); @@ -901,7 +906,7 @@ TEST(TOMLMiscellaneousTests, MultiLineLiteralEscapeCharsText) EXPECT_EQ(toml_parser::QuoteText("abc\rdef", toml_parser::EQuoteRequest::multi_line_literal_text), "'''abc\rdef'''"); } -TEST(TOMLMiscellaneousTests, MultiLineLiteralControlCharsText) +TEST(Miscellaneous, MultiLineLiteralControlCharsText) { // All become quoted insteda of literal (due to control character). EXPECT_EQ(toml_parser::QuoteText(std::string(1, '\0'), toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"\\u0000\"\"\""); @@ -942,7 +947,7 @@ TEST(TOMLMiscellaneousTests, MultiLineLiteralControlCharsText) // 0080... and higher are treated as unicode character (quotation needed) } -TEST(TOMLMiscellaneousTests, ExtractBareKeyName) +TEST(Miscellaneous, ExtractBareKeyName) { EXPECT_EQ(toml_parser::ExtractKeyName(""), ""); EXPECT_EQ(toml_parser::ExtractKeyName("abc.def"), "def"); @@ -955,7 +960,7 @@ TEST(TOMLMiscellaneousTests, ExtractBareKeyName) EXPECT_EQ(toml_parser::ExtractKeyName("1234"), "1234"); } -TEST(TOMLMiscellaneousTests, ExtractQuotedKeyName) +TEST(Miscellaneous, ExtractQuotedKeyName) { EXPECT_EQ(toml_parser::ExtractKeyName("\"\""), ""); EXPECT_EQ(toml_parser::ExtractKeyName("\"abc\""), "abc"); @@ -968,7 +973,7 @@ TEST(TOMLMiscellaneousTests, ExtractQuotedKeyName) EXPECT_EQ(toml_parser::ExtractKeyName("abc\"def\""), ""); // Failure } -TEST(TOMLMiscellaneousTests, ExtractLiteralKeyName) +TEST(Miscellaneous, ExtractLiteralKeyName) { EXPECT_EQ(toml_parser::ExtractKeyName("''"), ""); EXPECT_EQ(toml_parser::ExtractKeyName("'abc'"), "abc"); @@ -982,7 +987,7 @@ TEST(TOMLMiscellaneousTests, ExtractLiteralKeyName) EXPECT_EQ(toml_parser::ExtractKeyName("abc'def"), ""); // Failure } -TEST(TOMLMiscellaneousTests, ExtractIndexKeyName) +TEST(Miscellaneous, ExtractIndexKeyName) { EXPECT_EQ(toml_parser::ExtractKeyName("[0]"), "0"); EXPECT_EQ(toml_parser::ExtractKeyName("[0][1]"), "1"); diff --git a/tests/unit_tests/toml_parser/parser_tests.cpp b/tests/unit_tests/toml_parser/parser_tests.cpp index 3e20625..e16ee84 100644 --- a/tests/unit_tests/toml_parser/parser_tests.cpp +++ b/tests/unit_tests/toml_parser/parser_tests.cpp @@ -13,6 +13,7 @@ ********************************************************************************/ #include +#include "../../../global/localmemmgr.h" #include "../../../sdv_services/core/toml_parser/parser_toml.h" #include "../../../sdv_services/core/toml_parser/parser_node_toml.h" @@ -444,32 +445,32 @@ TEST(NestedContent, InlineTable) TEST(NestedContent, InlineTableBreakLine) { // The following are not allowed - EXPECT_THROW(toml_parser::CParser(R"code( + EXPECT_THROW(toml_parser::CParser(R"toml( table = { a = 1, b = 2, c = 3, d = 4 } -)code"), sdv::toml::XTOMLParseException); - EXPECT_THROW(toml_parser::CParser(R"code( +)toml"), sdv::toml::XTOMLParseException); + EXPECT_THROW(toml_parser::CParser(R"toml( table = { a = 1, b = 2 ,c = 3, d = 4 } -)code"), sdv::toml::XTOMLParseException); +)toml"), sdv::toml::XTOMLParseException); // Line breaks are allowed when part of an array or have multi-line strings - EXPECT_NO_THROW(toml_parser::CParser(R"code( + EXPECT_NO_THROW(toml_parser::CParser(R"toml( array = [{ a = 1, b = 2}, {c = 3, d = 4}] -)code")); - EXPECT_NO_THROW(toml_parser::CParser(R"code( +)toml")); + EXPECT_NO_THROW(toml_parser::CParser(R"toml( table = { a = 1, b = [2, 3, 4, 5], c = 6, d = 7} -)code")); - EXPECT_NO_THROW(toml_parser::CParser(R"code( +)toml")); + EXPECT_NO_THROW(toml_parser::CParser(R"toml( table = { x = "abc", y = """def- ghi""", z = "jkl" } -)code")); - EXPECT_NO_THROW(toml_parser::CParser(R"code( +)toml")); + EXPECT_NO_THROW(toml_parser::CParser(R"toml( table = { x = 'abc', y = '''def- ghi''', z = 'jkl' } -)code")); +)toml")); } TEST(SpecialCases, Keys) @@ -865,6 +866,60 @@ TEST(Ordering, TableAray) EXPECT_EQ(ptrArray->Get(uiIndex)->Cast()->Direct("a")->GetValue(), (int64_t) uiIndex); } +TEST(Ordering, TableArayWithTables) +{ + using namespace std::string_literals; + toml_parser::CParser parser(R"( + [topTable] + [[topTable.tableArray]] + [topTable.tableArray.MyTable] + a = 0 + [[topTable.tableArray]] + [topTable.tableArray.MyTable] + a = 1 + [[topTable.tableArray]] + [topTable.tableArray.MyTable] + a = 2 + [[topTable.tableArray]] + [topTable.tableArray.MyTable] + a = 3 + [[topTable.tableArray]] + [topTable.tableArray.MyTable] + a = 4 + [[topTable.tableArray]] + [topTable.tableArray.MyTable] + a = 5 + [[topTable.tableArray]] + [topTable.tableArray.MyTable] + a = 6 + [[topTable.tableArray]] + [topTable.tableArray.MyTable] + a = 7 + [[topTable.tableArray]] + [topTable.tableArray.MyTable] + a = 8 + [[topTable.tableArray]] + [topTable.tableArray.MyTable] + a = 9 + [[topTable.tableArray]] + [topTable.tableArray.MyTable] + a = 10 + [[topTable.tableArray]] + [topTable.tableArray.MyTable] + a = 11 + )"s); + + auto tableArray = parser.Root().Direct("topTable.tableArray"); + + ASSERT_NE(tableArray, nullptr); + auto ptrArray = tableArray->Cast(); + EXPECT_EQ(ptrArray->GetCount(), 12u); + for (uint32_t uiIndex = 0; uiIndex < ptrArray->GetCount(); uiIndex++) + { + EXPECT_EQ(ptrArray->Get(uiIndex)->Cast()->Direct("MyTable.a")->GetValue(), (int64_t)uiIndex); + } +} + TEST(Ordering, NodeGetDirect) { using namespace std::string_literals; diff --git a/tests/unit_tests/toml_parser/statement_boundary_detection.cpp b/tests/unit_tests/toml_parser/statement_boundary_detection.cpp index 873a30e..695f6c2 100644 --- a/tests/unit_tests/toml_parser/statement_boundary_detection.cpp +++ b/tests/unit_tests/toml_parser/statement_boundary_detection.cpp @@ -14,6 +14,7 @@ #include #include #include +#include "../../../global/localmemmgr.h" #include "../../../sdv_services/core/toml_parser/lexer_toml.h" #include "../../../sdv_services/core/toml_parser/exception.h" #include "../../../sdv_services/core/toml_parser/miscellaneous.h" @@ -445,7 +446,7 @@ bool FindAndExtendToken(toml_parser::CLexer& rlexer, const std::string& rssKey, return false; // When coming here, the key was not found } -TEST(TOMLLexerStatementBoundaryTests, CheckEmptyRange) +TEST(StatementBoundary, CheckEmptyRange) { std::string ssEmpty; @@ -459,11 +460,11 @@ TEST(TOMLLexerStatementBoundaryTests, CheckEmptyRange) EXPECT_THROW(lexerEmpty.SmartExtendNodeRange(rangeEmpty), sdv::toml::XTOMLParseException); } -TEST(TOMLLexerStatementBoundaryTests, CheckInvalidRange) +TEST(StatementBoundary, CheckInvalidRange) { - std::string ssCode = R"code( + std::string ssCode = R"toml( token_a = 10 -)code"; +)toml"; std::string ssOther = ssCode; // Process the code @@ -477,11 +478,11 @@ token_a = 10 EXPECT_THROW(lexerCode.SmartExtendNodeRange(rangeOther), sdv::toml::XTOMLParseException); } -TEST(TOMLLexerStatementBoundaryTests, StandardIntegerAssignment) +TEST(StatementBoundary, StandardIntegerAssignment) { - std::string ssCode = R"code( + std::string ssCode = R"toml( token_a = 10 -)code"; +)toml"; // Process the code toml_parser::CLexer lexerCode(ssCode); @@ -497,11 +498,11 @@ token_a = 10 toml_parser::ETokenCategory::token_syntax_new_line})); } -TEST(TOMLLexerStatementBoundaryTests, StandardStringAssignment) +TEST(StatementBoundary, StandardStringAssignment) { - std::string ssCode = R"code( + std::string ssCode = R"toml( token_b = "abc" -)code"; +)toml"; // Process the code toml_parser::CLexer lexerCode(ssCode); @@ -516,16 +517,16 @@ token_b = "abc" toml_parser::ETokenCategory::token_syntax_new_line})); } -TEST(TOMLLexerStatementBoundaryTests, AssignmentWithIndependentComment) +TEST(StatementBoundary, AssignmentWithIndependentComment) { - std::string ssCode = R"code( + std::string ssCode = R"toml( token_c = 30.1 # middle followed by double lines token_d = "def" -)code"; +)toml"; // Process the code toml_parser::CLexer lexerCode(ssCode); @@ -541,9 +542,9 @@ token_d = "def" toml_parser::ETokenCategory::token_syntax_new_line})); } -TEST(TOMLLexerStatementBoundaryTests, AssignmentCommentsNotPartOfIt) +TEST(StatementBoundary, AssignmentCommentsNotPartOfIt) { - std::string ssCode = R"code( + std::string ssCode = R"toml( token_c = 30.1 # middle followed by double lines @@ -554,7 +555,7 @@ token_d = "def" # before # more before token_e = [10, 20, 30] -)code"; +)toml"; // Process the code toml_parser::CLexer lexerCode(ssCode); @@ -570,9 +571,9 @@ token_e = [10, 20, 30] toml_parser::ETokenCategory::token_syntax_new_line})); } -TEST(TOMLLexerStatementBoundaryTests, ArrayAssignmentWithPreceedingComment) +TEST(StatementBoundary, ArrayAssignmentWithPreceedingComment) { - std::string ssCode = R"code( + std::string ssCode = R"toml( token_d = "def" # before @@ -581,7 +582,7 @@ token_e = [10, 20, 30] token_f = "ghi" # after # more after -)code"; +)toml"; // Process the code toml_parser::CLexer lexerCode(ssCode); @@ -609,14 +610,14 @@ token_f = "ghi" # after toml_parser::ETokenCategory::token_syntax_new_line})); } -TEST(TOMLLexerStatementBoundaryTests, AssignmentWithFollowingComment) +TEST(StatementBoundary, AssignmentWithFollowingComment) { - std::string ssCode = R"code( + std::string ssCode = R"toml( token_f = "ghi" # after # more after # belonging to next [token_g] -)code"; +)toml"; // Process the code toml_parser::CLexer lexerCode(ssCode); @@ -636,9 +637,9 @@ token_f = "ghi" # after toml_parser::ETokenCategory::token_syntax_new_line})); } -TEST(TOMLLexerStatementBoundaryTests, AssignmentWithFollowingComment2) +TEST(StatementBoundary, AssignmentWithFollowingComment2) { - std::string ssTOML = R"code( + std::string ssTOML = R"toml( # This is a separate comment with several line-breaks before. @@ -661,7 +662,7 @@ value = "this is the value text" # Comment following the value. # This is also a separate comment. # Followed by this text on the same line. -# And another text on a separate line.)code"; +# And another text on a separate line.)toml"; // Process the code toml_parser::CLexer lexerCode(ssTOML); @@ -701,15 +702,15 @@ value = "this is the value text" # Comment following the value. toml_parser::ETokenCategory::token_syntax_new_line})); } -TEST(TOMLLexerStatementBoundaryTests, TableAssignmentWithDedicatedComment) +TEST(StatementBoundary, TableAssignmentWithDedicatedComment) { - std::string ssCode = R"code( + std::string ssCode = R"toml( token_f = "ghi" # after # more after # belonging to next [token_g] -)code"; +)toml"; // Process the code toml_parser::CLexer lexerCode(ssCode); @@ -725,14 +726,14 @@ token_f = "ghi" # after toml_parser::ETokenCategory::token_syntax_new_line})); } -TEST(TOMLLexerStatementBoundaryTests, AssignmentWithFollowingCommentWithoutIndentation) +TEST(StatementBoundary, AssignmentWithFollowingCommentWithoutIndentation) { - std::string ssCode = R"code( + std::string ssCode = R"toml( token_h = "jkl"#after without whitespace # more after due to following empty line [token_i] -)code"; +)toml"; // Process the code toml_parser::CLexer lexerCode(ssCode); @@ -751,16 +752,16 @@ token_h = "jkl"#after without whitespace toml_parser::ETokenCategory::token_syntax_new_line})); } -TEST(TOMLLexerStatementBoundaryTests, AssignmentCommentsExcluded) +TEST(StatementBoundary, AssignmentCommentsExcluded) { - std::string ssCode = R"code( + std::string ssCode = R"toml( token_h = "jkl"#after without whitespace # more after due to following empty line [token_i] # not after token_j = 20 -)code"; +)toml"; // Process the code toml_parser::CLexer lexerCode(ssCode); @@ -773,13 +774,13 @@ token_j = 20 toml_parser::ETokenCategory::token_syntax_new_line})); } -TEST(TOMLLexerStatementBoundaryTests, SmartExtendTokenRange) +TEST(StatementBoundary, SmartExtendTokenRange) { - std::string ssCode = R"code( + std::string ssCode = R"toml( [token_i] # not after token_j = 20 -)code"; +)toml"; // Process the code toml_parser::CLexer lexerCode(ssCode); @@ -796,12 +797,12 @@ token_j = 20 toml_parser::ETokenCategory::token_syntax_new_line})); } -TEST(TOMLLexerStatementBoundaryTests, StandardAssignmentWithIndentation) +TEST(StatementBoundary, StandardAssignmentWithIndentation) { - std::string ssCode = R"code( + std::string ssCode = R"toml( token_k = 30 -)code"; +)toml"; // Process the code toml_parser::CLexer lexerCode(ssCode); @@ -818,11 +819,11 @@ TEST(TOMLLexerStatementBoundaryTests, StandardAssignmentWithIndentation) toml_parser::ETokenCategory::token_syntax_new_line})); } -TEST(TOMLLexerStatementBoundaryTests, ArrayOfStringsAssignment) +TEST(StatementBoundary, ArrayOfStringsAssignment) { - std::string ssCode = R"code( + std::string ssCode = R"toml( token_l = ["abc", "def", "ghi"] -)code"; +)toml"; // Process the code toml_parser::CLexer lexerCode(ssCode); @@ -846,16 +847,16 @@ TEST(TOMLLexerStatementBoundaryTests, ArrayOfStringsAssignment) toml_parser::ETokenCategory::token_syntax_new_line})); } -TEST(TOMLLexerStatementBoundaryTests, InlineTableAssignmentWithIndependentComment) +TEST(StatementBoundary, InlineTableAssignmentWithIndependentComment) { - std::string ssCode = R"code( + std::string ssCode = R"toml( token_m = {x = 10, str = "gfh"} # middle followed by double newlines token_n = 100 -)code"; +)toml"; // Process the code toml_parser::CLexer lexerCode(ssCode); @@ -885,9 +886,9 @@ TEST(TOMLLexerStatementBoundaryTests, InlineTableAssignmentWithIndependentCommen toml_parser::ETokenCategory::token_syntax_new_line})); } -TEST(TOMLLexerStatementBoundaryTests, IndentedAssignmentNoComments) +TEST(StatementBoundary, IndentedAssignmentNoComments) { - std::string ssCode = R"code( + std::string ssCode = R"toml( # begin followed by double newlines @@ -896,7 +897,7 @@ TEST(TOMLLexerStatementBoundaryTests, IndentedAssignmentNoComments) # before # more before token_o = 123.456 -)code"; +)toml"; // Process the code toml_parser::CLexer lexerCode(ssCode); @@ -913,15 +914,15 @@ TEST(TOMLLexerStatementBoundaryTests, IndentedAssignmentNoComments) toml_parser::ETokenCategory::token_syntax_new_line})); } -TEST(TOMLLexerStatementBoundaryTests, IndentedAssignmentWithCommentsBefore) +TEST(StatementBoundary, IndentedAssignmentWithCommentsBefore) { - std::string ssCode = R"code( + std::string ssCode = R"toml( # before # more before token_o = 123.456 -)code"; +)toml"; // Process the code toml_parser::CLexer lexerCode(ssCode); @@ -944,15 +945,15 @@ TEST(TOMLLexerStatementBoundaryTests, IndentedAssignmentWithCommentsBefore) toml_parser::ETokenCategory::token_syntax_new_line})); } -TEST(TOMLLexerStatementBoundaryTests, IndentedBooleanArrayAssignmentWithDedicatedComments) +TEST(StatementBoundary, IndentedBooleanArrayAssignmentWithDedicatedComments) { - std::string ssCode = R"code( + std::string ssCode = R"toml( token_p = [true, false] # after # more after # belonging to next token_q = "next" -)code"; +)toml"; // Process the code toml_parser::CLexer lexerCode(ssCode); @@ -978,15 +979,15 @@ TEST(TOMLLexerStatementBoundaryTests, IndentedBooleanArrayAssignmentWithDedicate toml_parser::ETokenCategory::token_syntax_new_line})); } -TEST(TOMLLexerStatementBoundaryTests, IndentedAssignmentWithDedicatedCommentsBefore) +TEST(StatementBoundary, IndentedAssignmentWithDedicatedCommentsBefore) { - std::string ssCode = R"code( + std::string ssCode = R"toml( token_p = [true, false] # after # more after # belonging to next token_q = "next" -)code"; +)toml"; // Process the code toml_parser::CLexer lexerCode(ssCode); @@ -1006,14 +1007,14 @@ TEST(TOMLLexerStatementBoundaryTests, IndentedAssignmentWithDedicatedCommentsBef toml_parser::ETokenCategory::token_syntax_new_line})); } -TEST(TOMLLexerStatementBoundaryTests, IndentedAssignmentFollowedByCommentWithoutSpace) +TEST(StatementBoundary, IndentedAssignmentFollowedByCommentWithoutSpace) { - std::string ssCode = R"code( + std::string ssCode = R"toml( token_r =987#after without whitespace # more after due to following empty line [token_s] -)code"; +)toml"; // Process the code toml_parser::CLexer lexerCode(ssCode); @@ -1033,13 +1034,13 @@ TEST(TOMLLexerStatementBoundaryTests, IndentedAssignmentFollowedByCommentWithout toml_parser::ETokenCategory::token_syntax_new_line})); } -TEST(TOMLLexerStatementBoundaryTests, IndentedTableWithoutComments) +TEST(StatementBoundary, IndentedTableWithoutComments) { - std::string ssCode = R"code( + std::string ssCode = R"toml( [token_s] # not after [token_t] -)code"; +)toml"; // Process the code toml_parser::CLexer lexerCode(ssCode); @@ -1053,16 +1054,16 @@ TEST(TOMLLexerStatementBoundaryTests, IndentedTableWithoutComments) toml_parser::ETokenCategory::token_syntax_new_line})); } -TEST(TOMLLexerStatementBoundaryTests, IndentedTableWithAndWithoutComments) +TEST(StatementBoundary, IndentedTableWithAndWithoutComments) { - std::string ssCode = R"code( + std::string ssCode = R"toml( [token_s] # not after [token_t] # comment before a table member (belongs to token_bb) token_u.token_aa.token_bb = 10 # table token_u has a table token_aa which has a value token_bb -)code"; +)toml"; // Process the code toml_parser::CLexer lexerCode(ssCode); @@ -1080,12 +1081,12 @@ token_u.token_aa.token_bb = 10 # table token_u has a table token_aa which ha toml_parser::ETokenCategory::token_syntax_new_line})); } -TEST(TOMLLexerStatementBoundaryTests, InlineTableWithParentChildAssignment) +TEST(StatementBoundary, InlineTableWithParentChildAssignment) { - std::string ssCode = R"code( + std::string ssCode = R"toml( # comment before a table member (belongs to token_bb) token_u.token_aa.token_bb = 10 # table token_u has a table token_aa which has a value token_bb -)code"; +)toml"; // Process the code toml_parser::CLexer lexerCode(ssCode); @@ -1116,9 +1117,9 @@ token_u.token_aa.token_bb = 10 # table token_u has a table token_aa which ha toml_parser::ETokenCategory::token_syntax_new_line})); } -TEST(TOMLLexerStatementBoundaryTests, ComplexInlineTableWithParentChildAssignmentOfTablesAndMultiDimensionalArrays) +TEST(StatementBoundary, ComplexInlineTableWithParentChildAssignmentOfTablesAndMultiDimensionalArrays) { - std::string ssCode = R"code( + std::string ssCode = R"toml( # Super inline table with sub-tables and arrays token_u.token_aa.token_cc = { dd = { ee = 10, ff = 11 }, # this is the comment for dd gg = [{hh = 1, ii = 2}, @@ -1128,7 +1129,7 @@ token_u.token_aa.token_cc = { dd = { ee = 10, ff = 11 }, # this is the commen # and this as well jj = [["abc", "def"], [1, 2, 3], []]} -)code"; +)toml"; // NOTE EVE 22.10.2025: the value token_u.token_aa.token_cc.jj is the last value in the table. This means that the scope of // the value included the comma before the value jj (following value gg). This has the consequence, that the comments, which @@ -1292,9 +1293,9 @@ token_u.token_aa.token_cc = { dd = { ee = 10, ff = 11 }, # this is the commen toml_parser::ETokenCategory::token_syntax_array_close})); } -TEST(TOMLLexerStatementBoundaryTests, ArrayOfTables) +TEST(StatementBoundary, ArrayOfTables) { - std::string ssCode = R"code( + std::string ssCode = R"toml( [[token_v]] token_kk = 10 token_ll = 20 @@ -1303,7 +1304,7 @@ token_mm = 30 [[token_v]] token_kk = 110 token_ll = 120 -)code"; +)toml"; // Process the code toml_parser::CLexer lexerCode(ssCode); @@ -1316,15 +1317,15 @@ token_ll = 120 toml_parser::ETokenCategory::token_syntax_new_line})); } -TEST(TOMLLexerStatementBoundaryTests, ParentChildTable) +TEST(StatementBoundary, ParentChildTable) { - std::string ssCode = R"code( + std::string ssCode = R"toml( [[token_v]] token_kk = 110 token_ll = 120 # Comments before (belongs ot child only) [token_x.token_nn] # Comments following (belongs to child only) -)code"; +)toml"; // Process the code toml_parser::CLexer lexerCode(ssCode); diff --git a/tests/unit_tests/trace_fifo/fifo_test.cpp b/tests/unit_tests/trace_fifo/fifo_test.cpp index 034b1c3..66a87d9 100644 --- a/tests/unit_tests/trace_fifo/fifo_test.cpp +++ b/tests/unit_tests/trace_fifo/fifo_test.cpp @@ -417,7 +417,7 @@ TEST(TraceFifoTest, Simple_Stream_Monitor) { std::stringstream sstreamWriter; CTraceFifoStreamBuffer fifoWriterStreamBuf(9999); - fifoWriterStreamBuf.Open(1000, static_cast(ETraceFifoOpenFlags::force_create)); + fifoWriterStreamBuf.Open(5000, static_cast(ETraceFifoOpenFlags::force_create)); EXPECT_TRUE(fifoWriterStreamBuf.IsOpened()); fifoWriterStreamBuf.InterceptStream(sstreamWriter); CTraceFifoReader fifoReader(9999); diff --git a/tests/unit_tests/unique_id/CMakeLists.txt b/tests/unit_tests/unique_id/CMakeLists.txt new file mode 100644 index 0000000..c61600c --- /dev/null +++ b/tests/unit_tests/unique_id/CMakeLists.txt @@ -0,0 +1,41 @@ +#******************************************************************************* +# Copyright (c) 2025-2026 ZF Friedrichshafen AG +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# +# Contributors: +# Erik Verhoeven - initial API and implementation +#******************************************************************************* + +# Define project +project(UniqueIDTests VERSION 1.0 LANGUAGES CXX) + +# Define target +add_executable(UnitTest_UniqueID unique_id_test.cpp) + +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + target_link_libraries(UnitTest_UniqueID GTest::GTest) + if (WIN32) + target_link_libraries(UnitTest_UniqueID Ws2_32 Winmm Rpcrt4.lib) + else() + target_link_libraries(UnitTest_UniqueID ${CMAKE_DL_LIBS} rt) + endif() +else() + target_link_libraries(UnitTest_UniqueID GTest::GTest Rpcrt4.lib) +endif() + +# Add the test +add_test(NAME UnitTest_UniqueID COMMAND UnitTest_UniqueID WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + +# Execute the test +add_custom_command(TARGET UnitTest_UniqueID POST_BUILD + COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake "$" --gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_UniqueID.xml + VERBATIM +) + +# The unit-test project depends on a proper compilation of sdv components before +add_dependencies(UnitTest_UniqueID dependency_sdv_components) diff --git a/tests/unit_tests/unique_id/unique_id_test.cpp b/tests/unit_tests/unique_id/unique_id_test.cpp new file mode 100644 index 0000000..71d35ec --- /dev/null +++ b/tests/unit_tests/unique_id/unique_id_test.cpp @@ -0,0 +1,85 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 ZF Friedrichshafen AG + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Erik Verhoeven - initial API and implementation + ********************************************************************************/ + +#include +#include +#include "../../../global/process_watchdog.h" +#include "../../../global/unique_id.h" + +#if defined(_WIN32) && defined(_UNICODE) +extern "C" int wmain(int argc, wchar_t* argv[]) +#else +extern "C" int main(int argc, char* argv[]) +#endif +{ + CProcessWatchdog watchdog; + + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} + +TEST(UniqueID, TestSmallID) +{ + CUniqueID generator; + + // Max 15 ids possible + std::set setUsed; + int iNumber; + for (size_t n = 0; n < 15; n++) + { + iNumber = generator.Generate(); + EXPECT_NE(iNumber, 0); + EXPECT_EQ(setUsed.find(iNumber), setUsed.end()); + setUsed.insert(iNumber); + } + + // One more ID should fail + iNumber = generator.Generate(); + EXPECT_EQ(iNumber, 0); +} + +TEST(UniqueID, TestSmallID2) +{ + CUniqueID generator; + + // Max 255 ids possible + std::set setUsed; + uint32_t uiNumber = 0; + for (size_t n = 0; n < 255; n++) + { + uiNumber = generator.Generate(); + EXPECT_NE(uiNumber, 0u); + EXPECT_EQ(setUsed.find(uiNumber), setUsed.end()); + setUsed.insert(uiNumber); + } + + // One more ID should fail + uiNumber = generator.Generate(); + EXPECT_EQ(uiNumber, 0u); +} + +TEST(UniqueID, TestLargeID) +{ + CUniqueID generator; + + // Run at the most 20000 times and check whether the number occurs multiple times + std::set setUsed; + uint64_t uiNumber; + for (size_t n = 0; n < 20000; n++) + { + uiNumber = generator.Generate(); + EXPECT_NE(uiNumber, 0u); + EXPECT_EQ(setUsed.find(uiNumber), setUsed.end()); + setUsed.insert(uiNumber); + } +} diff --git a/tests/unit_tests/unix_sockets/CMakeLists.txt b/tests/unit_tests/unix_sockets/CMakeLists.txt index 65c71a0..09d4315 100644 --- a/tests/unit_tests/unix_sockets/CMakeLists.txt +++ b/tests/unit_tests/unix_sockets/CMakeLists.txt @@ -33,7 +33,7 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") ${CMAKE_THREAD_LIBS_INIT} stdc++fs ${CMAKE_DL_LIBS} rt - uds_unix_sockets + uds_unix_sockets_static ) else() target_link_libraries(UnitTest_UnixSocketConnectTests @@ -41,7 +41,7 @@ else() Ws2_32 Winmm Rpcrt4.lib - uds_unix_sockets + uds_unix_sockets_static ) endif() @@ -50,13 +50,11 @@ add_test(NAME UnitTest_UnixSocketConnectTests add_dependencies(UnitTest_UnixSocketConnectTests dependency_sdv_components) -if ((NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (NOT WIN32)) add_custom_command(TARGET UnitTest_UnixSocketConnectTests POST_BUILD COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake "$" --gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_UnixSocketConnectTests.xml VERBATIM ) -endif() endif() \ No newline at end of file diff --git a/tests/unit_tests/unix_sockets/uds_connect_tests.cpp b/tests/unit_tests/unix_sockets/uds_connect_tests.cpp index a035fbe..3f2110c 100644 --- a/tests/unit_tests/unix_sockets/uds_connect_tests.cpp +++ b/tests/unit_tests/unix_sockets/uds_connect_tests.cpp @@ -204,6 +204,25 @@ private: // Small helper: convert server connectString to client connectString static std::string MakeClientCS(std::string cs) { + const std::string providerKey = "ConnectString = \""; + auto p = cs.find(providerKey); + if (p != std::string::npos) + { + p += providerKey.size(); + auto e = cs.find('"', p); + if (e != std::string::npos) + { + std::string inner = cs.substr(p, e - p); + const std::string fromIn = "role=server"; + const std::string toIn = "role=client"; + auto posIn = inner.find(fromIn); + if (posIn != std::string::npos) + inner.replace(posIn, fromIn.size(), toIn); + cs.replace(p, e - p, inner); + return cs; + } + } + const std::string from = "role=server"; const std::string to = "role=client"; auto pos = cs.find(from); @@ -215,6 +234,27 @@ static std::string MakeClientCS(std::string cs) static std::string ExtractPathFromCS(const std::string& cs) { + const std::string providerKey = "ConnectString = \""; + auto pProvider = cs.find(providerKey); + if (pProvider != std::string::npos) + { + pProvider += providerKey.size(); + auto eProvider = cs.find('"', pProvider); + if (eProvider != std::string::npos) + { + std::string inner = cs.substr(pProvider, eProvider - pProvider); + const std::string keyInner = "path="; + auto pInner = inner.find(keyInner); + if (pInner != std::string::npos) + { + auto startInner = pInner + keyInner.size(); + auto endInner = inner.find(';', startInner); + if (endInner == std::string::npos) endInner = inner.size(); + return inner.substr(startInner, endInner - startInner); + } + } + } + // search "path=" in connect-string const std::string key = "path="; auto p = cs.find(key); @@ -225,6 +265,21 @@ static std::string ExtractPathFromCS(const std::string& cs) return cs.substr(start, end - start); } +static std::string MakeServerCS(const std::string& cs) +{ + const std::string path = ExtractPathFromCS(cs); + if (!path.empty()) + return "proto=uds;role=server;path=" + path + ";"; + + std::string raw = cs; + const std::string from = "role=client"; + const std::string to = "role=server"; + auto pos = raw.find(from); + if (pos != std::string::npos) + raw.replace(pos, from.size(), to); + return raw; +} + static std::string MakeRandomSuffix() { std::mt19937_64 rng{std::random_device{}()}; @@ -241,7 +296,7 @@ TEST(UnixSocketIPC, Instantiate) ASSERT_TRUE(appcontrol.Startup("")); CUnixDomainSocketsChannelMgnt mgr; - EXPECT_NO_THROW(mgr.Initialize("")); + EXPECT_NO_THROW(mgr.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::initialized); @@ -258,7 +313,7 @@ TEST(UnixSocketIPC, ChannelConfigString) ASSERT_TRUE(appcontrol.Startup("")); CUnixDomainSocketsChannelMgnt mgr; - EXPECT_NO_THROW(mgr.Initialize("")); + EXPECT_NO_THROW(mgr.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::initialized); @@ -275,7 +330,7 @@ TEST(UnixSocketIPC, CreateRandomEndpoint) CUnixDomainSocketsChannelMgnt mgr; // Create an endpoint. - EXPECT_NO_THROW(mgr.Initialize("")); + EXPECT_NO_THROW(mgr.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::initialized); sdv::ipc::SChannelEndpoint sChannelEndpoint = mgr.CreateEndpoint(""); EXPECT_NE(sChannelEndpoint.pConnection, nullptr); @@ -297,7 +352,7 @@ TEST(UnixSocketIPC, BasicConnectDisconnect) // Create and initialize UDS manager CUnixDomainSocketsChannelMgnt mgr; - EXPECT_NO_THROW(mgr.Initialize("")); + EXPECT_NO_THROW(mgr.Initialize(sdv::SObjectInfo())); EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running)); ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running); @@ -305,7 +360,7 @@ TEST(UnixSocketIPC, BasicConnectDisconnect) auto ep = mgr.CreateEndpoint(""); ASSERT_FALSE(ep.ssConnectString.empty()); - std::string serverCS = ep.ssConnectString; + std::string serverCS = MakeServerCS(ep.ssConnectString); std::string clientCS = MakeClientCS(serverCS); // SERVER SIDE @@ -321,7 +376,7 @@ TEST(UnixSocketIPC, BasicConnectDisconnect) // CLIENT SIDE (thread) std::atomic clientResult{0}; std::atomic allowClientDisconnect{false}; - std::thread clientThread([&]{ + sdv::core::secure_thread clientThread([&]{ sdv::TObjectPtr clientObj = mgr.Access(clientCS); if (!clientObj) { clientResult = 1; return; } auto* clientConn = clientObj.GetInterface(); @@ -354,6 +409,9 @@ TEST(UnixSocketIPC, BasicConnectDisconnect) serverConn->Disconnect(); EXPECT_EQ(serverConn->GetConnectState(), sdv::ipc::EConnectState::disconnected); + // Release object wrappers before shutting down the manager/framework. + serverObj.Clear(); + // Shutdown Manager / Framework EXPECT_NO_THROW(mgr.Shutdown()); EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::destruction_pending); @@ -372,7 +430,7 @@ TEST(UnixSocketIPC, ReconnectAfterDisconnect_SamePath) //UDS manager CUnixDomainSocketsChannelMgnt mgr; - EXPECT_NO_THROW(mgr.Initialize("")); + EXPECT_NO_THROW(mgr.Initialize(sdv::SObjectInfo())); EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running)); ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running); @@ -380,7 +438,7 @@ TEST(UnixSocketIPC, ReconnectAfterDisconnect_SamePath) auto ep = mgr.CreateEndpoint(""); ASSERT_FALSE(ep.ssConnectString.empty()); - const std::string serverCS = ep.ssConnectString; + const std::string serverCS = MakeServerCS(ep.ssConnectString); const std::string clientCS = MakeClientCS(serverCS); // SESSION 1 @@ -397,7 +455,7 @@ TEST(UnixSocketIPC, ReconnectAfterDisconnect_SamePath) std::atomic clientResult{0}; std::atomic allowClientDisconnect{false}; - std::thread clientThread([&]{ + sdv::core::secure_thread clientThread([&]{ sdv::TObjectPtr clientObj = mgr.Access(clientCS); if (!clientObj) { clientResult = 1; return; } auto* clientConn = clientObj.GetInterface(); @@ -444,7 +502,7 @@ TEST(UnixSocketIPC, ReconnectAfterDisconnect_SamePath) std::atomic clientResult2{0}; std::atomic allowClientDisconnect2{false}; - std::thread clientThread2([&]{ + sdv::core::secure_thread clientThread2([&]{ sdv::TObjectPtr clientObj2 = mgr.Access(clientCS); if (!clientObj2) { clientResult2 = 1; return; } auto* clientConn2 = clientObj2.GetInterface(); @@ -490,7 +548,7 @@ TEST(UnixSocketIPC, OperationModeTransitions) sdv::app::CAppControl app; ASSERT_TRUE(app.Startup("")); CUnixDomainSocketsChannelMgnt mgr; - EXPECT_NO_THROW(mgr.Initialize("")); + EXPECT_NO_THROW(mgr.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::initialized); // configuring and then running @@ -514,7 +572,7 @@ TEST(UnixSocketIPC, CreateEndpoint_WithConfigAndPathClamping) app.SetRunningMode(); CUnixDomainSocketsChannelMgnt mgr; - EXPECT_NO_THROW(mgr.Initialize("")); + EXPECT_NO_THROW(mgr.Initialize(sdv::SObjectInfo())); EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running)); ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running); @@ -531,7 +589,7 @@ TEST(UnixSocketIPC, CreateEndpoint_WithConfigAndPathClamping) ASSERT_FALSE(ep.ssConnectString.empty()); // Checking if endpoint is server type and has an ok path - std::string serverCS = ep.ssConnectString; + std::string serverCS = MakeServerCS(ep.ssConnectString); std::string clientCS = MakeClientCS(serverCS); auto clampedPath = ExtractPathFromCS(serverCS); ASSERT_FALSE(clampedPath.empty()); @@ -572,7 +630,7 @@ TEST(UnixSocketIPC, Access_DefaultPath_ServerClientConnect) app.SetRunningMode(); CUnixDomainSocketsChannelMgnt mgr; - EXPECT_NO_THROW(mgr.Initialize("")); + EXPECT_NO_THROW(mgr.Initialize(sdv::SObjectInfo())); EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running)); ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running); @@ -613,13 +671,13 @@ TEST(UnixSocketIPC, WaitForConnection_InfiniteWait_SlowClient) app.SetRunningMode(); CUnixDomainSocketsChannelMgnt mgr; - EXPECT_NO_THROW(mgr.Initialize("")); + EXPECT_NO_THROW(mgr.Initialize(sdv::SObjectInfo())); EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running)); ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running); auto ep = mgr.CreateEndpoint(""); ASSERT_FALSE(ep.ssConnectString.empty()); - const std::string serverCS = ep.ssConnectString; + const std::string serverCS = MakeServerCS(ep.ssConnectString); const std::string clientCS = MakeClientCS(serverCS); sdv::TObjectPtr serverObj = mgr.Access(serverCS); @@ -629,7 +687,7 @@ TEST(UnixSocketIPC, WaitForConnection_InfiniteWait_SlowClient) CUDSConnectReceiver sRcvr; ASSERT_TRUE(serverConn->AsyncConnect(&sRcvr)); - std::thread delayedClient([&]{ + sdv::core::secure_thread delayedClient([&]{ std::this_thread::sleep_for(std::chrono::milliseconds(1000)); sdv::TObjectPtr clientObj = mgr.Access(clientCS); auto* clientConn = clientObj.GetInterface(); @@ -658,13 +716,13 @@ TEST(UnixSocketIPC, WaitForConnection_ZeroTimeout_BeforeAndAfter) app.SetRunningMode(); CUnixDomainSocketsChannelMgnt mgr; - EXPECT_NO_THROW(mgr.Initialize("")); + EXPECT_NO_THROW(mgr.Initialize(sdv::SObjectInfo())); EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running)); ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running); auto ep = mgr.CreateEndpoint(""); ASSERT_FALSE(ep.ssConnectString.empty()); - const std::string serverCS = ep.ssConnectString; + const std::string serverCS = MakeServerCS(ep.ssConnectString); const std::string clientCS = MakeClientCS(serverCS); sdv::TObjectPtr serverObj = mgr.Access(serverCS); @@ -700,7 +758,7 @@ ViewFilter = "Fatal")toml")); app.SetRunningMode(); CUnixDomainSocketsChannelMgnt mgr; - EXPECT_NO_THROW(mgr.Initialize("")); + EXPECT_NO_THROW(mgr.Initialize(sdv::SObjectInfo())); EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running)); ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running); @@ -734,12 +792,12 @@ TEST(UnixSocketIPC, ServerDisconnectPropagatesToClient) app.SetRunningMode(); CUnixDomainSocketsChannelMgnt mgr; - EXPECT_NO_THROW(mgr.Initialize("")); + EXPECT_NO_THROW(mgr.Initialize(sdv::SObjectInfo())); EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running)); ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running); auto ep = mgr.CreateEndpoint(""); - const std::string serverCS = ep.ssConnectString; + const std::string serverCS = MakeServerCS(ep.ssConnectString); const std::string clientCS = MakeClientCS(serverCS); sdv::TObjectPtr serverObj = mgr.Access(serverCS); @@ -776,12 +834,12 @@ TEST(UnixSocketIPC, ReconnectOnSameServerInstance) app.SetRunningMode(); CUnixDomainSocketsChannelMgnt mgr; - EXPECT_NO_THROW(mgr.Initialize("")); + EXPECT_NO_THROW(mgr.Initialize(sdv::SObjectInfo())); EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running)); ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running); auto ep = mgr.CreateEndpoint(""); - const std::string serverCS = ep.ssConnectString; + const std::string serverCS = MakeServerCS(ep.ssConnectString); const std::string clientCS = MakeClientCS(serverCS); sdv::TObjectPtr serverObj = mgr.Access(serverCS); @@ -827,14 +885,14 @@ TEST(UnixSocketIPC, DataPath_SimpleHello) app.SetRunningMode(); CUnixDomainSocketsChannelMgnt mgr; - EXPECT_NO_THROW(mgr.Initialize("")); + EXPECT_NO_THROW(mgr.Initialize(sdv::SObjectInfo())); EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running)); ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running); // Endpoint auto ep = mgr.CreateEndpoint(""); ASSERT_FALSE(ep.ssConnectString.empty()); - const std::string serverCS = ep.ssConnectString; + const std::string serverCS = MakeServerCS(ep.ssConnectString); const std::string clientCS = MakeClientCS(serverCS); // Server @@ -894,13 +952,13 @@ TEST(UnixSocketIPC, DataPath_MultiChunk_TwoBuffers) app.SetRunningMode(); CUnixDomainSocketsChannelMgnt mgr; - EXPECT_NO_THROW(mgr.Initialize("")); + EXPECT_NO_THROW(mgr.Initialize(sdv::SObjectInfo())); EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running)); ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running); auto ep = mgr.CreateEndpoint(""); ASSERT_FALSE(ep.ssConnectString.empty()); - const std::string serverCS = ep.ssConnectString; + const std::string serverCS = MakeServerCS(ep.ssConnectString); const std::string clientCS = MakeClientCS(serverCS); sdv::TObjectPtr serverObj = mgr.Access(serverCS); @@ -955,14 +1013,14 @@ TEST(UnixSocketIPC, DataPath_LargePayload_Fragmentation_Reassembly) ASSERT_TRUE(app.Startup("")); app.SetRunningMode(); CUnixDomainSocketsChannelMgnt mgr; - EXPECT_NO_THROW(mgr.Initialize("")); + EXPECT_NO_THROW(mgr.Initialize(sdv::SObjectInfo())); EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running)); ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running); // Endpoint auto ep = mgr.CreateEndpoint(""); ASSERT_FALSE(ep.ssConnectString.empty()); - const std::string serverCS = ep.ssConnectString; + const std::string serverCS = MakeServerCS(ep.ssConnectString); const std::string clientCS = MakeClientCS(serverCS); // Server @@ -1023,12 +1081,12 @@ TEST(UnixSocketIPC, DataPath_ZeroLengthChunks_ArePreserved) ASSERT_TRUE(app.Startup("")); app.SetRunningMode(); CUnixDomainSocketsChannelMgnt mgr; - EXPECT_NO_THROW(mgr.Initialize("")); + EXPECT_NO_THROW(mgr.Initialize(sdv::SObjectInfo())); EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running)); ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running); auto ep = mgr.CreateEndpoint(""); - const std::string serverCS = ep.ssConnectString; + const std::string serverCS = MakeServerCS(ep.ssConnectString); const std::string clientCS = MakeClientCS(serverCS); sdv::TObjectPtr serverObj = mgr.Access(serverCS); @@ -1086,12 +1144,12 @@ TEST(UnixSocketIPC, PeerCloseMidTransfer_ClientSeesDisconnected_AndSendMayFailOr app.SetRunningMode(); CUnixDomainSocketsChannelMgnt mgr; - ASSERT_NO_THROW(mgr.Initialize("")); + ASSERT_NO_THROW(mgr.Initialize(sdv::SObjectInfo())); ASSERT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running)); ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running); auto ep = mgr.CreateEndpoint(""); - const std::string serverCS = ep.ssConnectString; + const std::string serverCS = MakeServerCS(ep.ssConnectString); const std::string clientCS = MakeClientCS(serverCS); sdv::TObjectPtr serverObj = mgr.Access(serverCS); @@ -1119,7 +1177,7 @@ TEST(UnixSocketIPC, PeerCloseMidTransfer_ClientSeesDisconnected_AndSendMayFailOr ASSERT_NE(pSend, nullptr); std::atomic sendResult{true}; - std::thread t([&]{ + sdv::core::secure_thread t([&]{ sendResult.store(pSend->SendData(seq)); }); @@ -1148,7 +1206,7 @@ TEST(UnixSocketIPC, ClientCancelConnect_NoServer_CleansUpPromptly) ASSERT_TRUE(app.Startup("")); app.SetRunningMode(); CUnixDomainSocketsChannelMgnt mgr; - EXPECT_NO_THROW(mgr.Initialize("")); + EXPECT_NO_THROW(mgr.Initialize(sdv::SObjectInfo())); EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running)); ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running); @@ -1181,12 +1239,12 @@ TEST(UnixSocketIPC, ServerStartThenImmediateDisconnect_NoClient) ViewFilter = "Fatal")toml")); app.SetRunningMode(); CUnixDomainSocketsChannelMgnt mgr; - EXPECT_NO_THROW(mgr.Initialize("")); + EXPECT_NO_THROW(mgr.Initialize(sdv::SObjectInfo())); EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running)); ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running); auto ep = mgr.CreateEndpoint(""); - const std::string serverCS = ep.ssConnectString; + const std::string serverCS = MakeServerCS(ep.ssConnectString); sdv::TObjectPtr serverObj = mgr.Access(serverCS); ASSERT_TRUE(serverObj); @@ -1212,12 +1270,12 @@ TEST(UnixSocketIPC, CallbackThrowsInSetConnectState_DoesNotCrashTransport) ASSERT_TRUE(app.Startup("")); app.SetRunningMode(); CUnixDomainSocketsChannelMgnt mgr; - EXPECT_NO_THROW(mgr.Initialize("")); + EXPECT_NO_THROW(mgr.Initialize(sdv::SObjectInfo())); EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running)); ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running); auto ep = mgr.CreateEndpoint(""); - const std::string serverCS = ep.ssConnectString; + const std::string serverCS = MakeServerCS(ep.ssConnectString); const std::string clientCS = MakeClientCS(serverCS); sdv::TObjectPtr serverObj = mgr.Access(serverCS); @@ -1253,13 +1311,13 @@ TEST(UnixSocketIPC, RegisterStateEventCallback_MultipleCallbacksReceiveState) app.SetRunningMode(); CUnixDomainSocketsChannelMgnt mgr; - ASSERT_NO_THROW(mgr.Initialize("")); + ASSERT_NO_THROW(mgr.Initialize(sdv::SObjectInfo())); ASSERT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running)); ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running); // --- Setup server endpoint --- auto ep = mgr.CreateEndpoint(""); - const std::string serverCS = ep.ssConnectString; + const std::string serverCS = MakeServerCS(ep.ssConnectString); const std::string clientCS = MakeClientCS(serverCS); sdv::TObjectPtr serverObj = mgr.Access(serverCS); @@ -1312,13 +1370,13 @@ TEST(UnixSocketIPC, UnregisterStateEventCallback_RemovedListenerStopsReceiving) app.SetRunningMode(); CUnixDomainSocketsChannelMgnt mgr; - ASSERT_NO_THROW(mgr.Initialize("")); + ASSERT_NO_THROW(mgr.Initialize(sdv::SObjectInfo())); ASSERT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running)); ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running); // Endpoint auto ep = mgr.CreateEndpoint(""); - const std::string serverCS = ep.ssConnectString; + const std::string serverCS = MakeServerCS(ep.ssConnectString); const std::string clientCS = MakeClientCS(serverCS); // Server @@ -1376,4 +1434,60 @@ TEST(UnixSocketIPC, UnregisterStateEventCallback_RemovedListenerStopsReceiving) app.Shutdown(); } +// Reconnect race regression: a second AsyncConnect while connect worker is still initializing +// must return quickly and must not deadlock by joining an in-progress worker. +TEST(UnixSocketIPC, AsyncConnect_DoubleCallWhileInitializing_NoDeadlock) +{ + sdv::app::CAppControl app; + ASSERT_TRUE(app.Startup("")); + app.SetRunningMode(); + + CUnixDomainSocketsChannelMgnt mgr; + ASSERT_NO_THROW(mgr.Initialize(sdv::SObjectInfo())); + ASSERT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running)); + ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running); + + auto ep = mgr.CreateEndpoint(""); + ASSERT_FALSE(ep.ssConnectString.empty()); + + const std::string serverCS = MakeServerCS(ep.ssConnectString); + const std::string clientCS = MakeClientCS(serverCS); + + sdv::TObjectPtr serverObj = mgr.Access(serverCS); + ASSERT_TRUE(serverObj); + auto* serverConn = serverObj.GetInterface(); + ASSERT_NE(serverConn, nullptr); + + sdv::TObjectPtr clientObj = mgr.Access(clientCS); + ASSERT_TRUE(clientObj); + auto* clientConn = clientObj.GetInterface(); + ASSERT_NE(clientConn, nullptr); + + CUDSConnectReceiver sRcvr; + CUDSConnectReceiver cRcvr; + + ASSERT_TRUE(serverConn->AsyncConnect(&sRcvr)); + + auto t0 = std::chrono::steady_clock::now(); + bool secondAsync = serverConn->AsyncConnect(&sRcvr); + auto elapsedMs = std::chrono::duration_cast( + std::chrono::steady_clock::now() - t0).count(); + + EXPECT_FALSE(secondAsync); + EXPECT_LT(elapsedMs, 200); + + ASSERT_TRUE(clientConn->AsyncConnect(&cRcvr)); + EXPECT_TRUE(serverConn->WaitForConnection(5000)); + EXPECT_TRUE(clientConn->WaitForConnection(5000)); + + // Once connected, repeated AsyncConnect should be a no-op success. + EXPECT_TRUE(serverConn->AsyncConnect(&sRcvr)); + + clientConn->Disconnect(); + serverConn->Disconnect(); + + EXPECT_NO_THROW(mgr.Shutdown()); + app.Shutdown(); +} + #endif // defined __unix__ \ No newline at end of file diff --git a/tests/unit_tests/unix_tunnel/CMakeLists.txt b/tests/unit_tests/unix_tunnel/CMakeLists.txt index 2ca7a0d..c51ae33 100644 --- a/tests/unit_tests/unix_tunnel/CMakeLists.txt +++ b/tests/unit_tests/unix_tunnel/CMakeLists.txt @@ -35,7 +35,7 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") ${CMAKE_DL_LIBS} rt uds_unix_tunnel - uds_unix_sockets + uds_unix_sockets_static ) else() target_link_libraries(UnitTest_UnixTunnelConnectTests @@ -43,7 +43,7 @@ else() Ws2_32 Winmm Rpcrt4.lib uds_unix_tunnel - uds_unix_sockets + uds_unix_sockets_static ) endif() @@ -52,14 +52,12 @@ add_test(NAME UnitTest_UnixTunnelConnectTests add_dependencies(UnitTest_UnixTunnelConnectTests dependency_sdv_components) -if ((NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (NOT WIN32)) add_custom_command(TARGET UnitTest_UnixTunnelConnectTests POST_BUILD COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake "$" --gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_UnixTunnelConnectTests.xml VERBATIM ) -endif() add_executable(UnitTest_UnixTunnelChannelMgntTests unix_tunnel_channel_mgnt_tests.cpp @@ -77,7 +75,7 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") ${CMAKE_DL_LIBS} rt uds_unix_tunnel - uds_unix_sockets + uds_unix_sockets_static ) else() target_link_libraries(UnitTest_UnixTunnelChannelMgntTests @@ -86,7 +84,7 @@ else() Winmm Rpcrt4.lib uds_unix_tunnel - uds_unix_sockets + uds_unix_sockets_static ) endif() @@ -95,13 +93,11 @@ add_test(NAME UnitTest_UnixTunnelChannelMgntTests add_dependencies(UnitTest_UnixTunnelChannelMgntTests dependency_sdv_components) -if ((NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (NOT WIN32)) add_custom_command(TARGET UnitTest_UnixTunnelChannelMgntTests POST_BUILD COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake "$" --gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_UnixTunnelChannelMgntTests.xml VERBATIM ) -endif() endif() \ No newline at end of file diff --git a/tests/unit_tests/unix_tunnel/unix_tunnel_channel_mgnt_tests.cpp b/tests/unit_tests/unix_tunnel/unix_tunnel_channel_mgnt_tests.cpp index 9568e0c..3547b15 100644 --- a/tests/unit_tests/unix_tunnel/unix_tunnel_channel_mgnt_tests.cpp +++ b/tests/unit_tests/unix_tunnel/unix_tunnel_channel_mgnt_tests.cpp @@ -12,6 +12,10 @@ #include #include #include +#include +#include +#include +#include #include "../sdv_services/uds_unix_tunnel/channel_mgnt.h" #include "../sdv_services/uds_unix_tunnel/connection.h" @@ -81,6 +85,109 @@ private: sdv::sequence> m_lastData; bool m_received{ false }; }; +// Helper namespace +namespace tunnel_utils +{ + +inline std::string Expand(const std::string& in) +{ + // Simple expand for $HOME / $TMPDIR etc. + std::string out = in; + + auto replace_env = [&](const std::string& key, const char* env) + { + const char* val = std::getenv(env); + if (!val) return; + + auto pos = out.find(key); + if (pos != std::string::npos) + { + out.replace(pos, key.size(), val); + } + }; + + replace_env("$HOME", "HOME"); + replace_env("$TMPDIR", "TMPDIR"); + + return out; +} + +inline void EnsureParentDir(const std::string& full) +{ + auto p = full.find_last_of('/'); + if (p == std::string::npos) + return; + + std::filesystem::create_directories(full.substr(0, p)); +} + +inline std::string MakeShortUdsPath(const char* name) +{ + std::string base = "/tmp/sdv/"; + + EnsureParentDir(base); + + return base + name; +} + +inline std::string RandomHex() +{ + std::mt19937_64 r{std::random_device{}()}; + std::uniform_int_distribution d; + std::ostringstream oss; + oss << std::hex << d(r); + return oss.str(); +} + +inline std::string Unique(const char* prefix) +{ + return MakeShortUdsPath((std::string(prefix) + "_" + RandomHex() + ".sock").c_str()); +} + +inline std::string UniqueTunnel() +{ + return "t_" + RandomHex(); +} + +inline void SpinUntilServerArmed(sdv::ipc::IConnect* server) +{ + using namespace std::chrono; + const auto deadline = steady_clock::now() + milliseconds(500); + while (server->GetConnectState() == sdv::ipc::EConnectState::uninitialized && + steady_clock::now() < deadline) + { + std::this_thread::sleep_for(milliseconds(2)); + } +} + +} // namespace tunnel_utils + +using namespace tunnel_utils; + +struct EndpointClientPair +{ + sdv::ipc::IConnect* server = nullptr; // from ep.pConnection + sdv::TObjectPtr clientObj; + sdv::ipc::IConnect* client = nullptr; +}; + +static EndpointClientPair CreateEndpointClientPair( + CUnixTunnelChannelMgnt& mgr, + const sdv::ipc::SChannelEndpoint& ep) +{ + EndpointClientPair out; + + out.server = ep.pConnection + ? ep.pConnection->GetInterface() + : nullptr; + + out.clientObj = mgr.Access(ep.ssConnectString); + out.client = out.clientObj + ? out.clientObj.GetInterface() + : nullptr; + + return out; +} //Manager instantiate + lifecycle TEST(UnixTunnelChannelMgnt, InstantiateAndLifecycle) @@ -89,7 +196,7 @@ TEST(UnixTunnelChannelMgnt, InstantiateAndLifecycle) ASSERT_TRUE(app.Startup("")); CUnixTunnelChannelMgnt mgr; - EXPECT_NO_THROW(mgr.Initialize("")); + EXPECT_NO_THROW(mgr.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::initialized); EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::configuring)); @@ -105,6 +212,7 @@ TEST(UnixTunnelChannelMgnt, InstantiateAndLifecycle) } // CreateEndpoint -> Access(server/client) -> AsyncConnect -> Wait -> Disconnect + TEST(UnixTunnelChannelMgnt, BasicConnectDisconnect) { sdv::app::CAppControl app; @@ -112,63 +220,41 @@ TEST(UnixTunnelChannelMgnt, BasicConnectDisconnect) app.SetRunningMode(); CUnixTunnelChannelMgnt mgr; - EXPECT_NO_THROW(mgr.Initialize("")); - EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running)); - ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running); + mgr.Initialize(sdv::SObjectInfo()); + mgr.SetOperationMode(sdv::EOperationMode::running); - // Create a tunnel endpoint (server) - auto ep = mgr.CreateEndpoint(""); - ASSERT_NE(ep.pConnection, nullptr); + const std::string tunnel = "t_" + RandomHex(); + const std::string cs = + "proto=tunnel;path=" + Unique("tunnel_mgr_basic") + + ";tunnel=" + tunnel + ";"; + + auto ep = mgr.CreateEndpoint(cs); ASSERT_FALSE(ep.ssConnectString.empty()); - const std::string serverCS = ep.ssConnectString; - // Convert to client by role=client - std::string clientCS = serverCS; - { - const std::string from = "role=server"; - const std::string to = "role=client"; - auto pos = clientCS.find(from); - if (pos != std::string::npos) - clientCS.replace(pos, from.size(), to); - } + auto pair = CreateEndpointClientPair(mgr, ep); - // SERVER - sdv::TObjectPtr serverObj = mgr.Access(serverCS); - ASSERT_TRUE(serverObj); - auto* serverConn = serverObj.GetInterface(); - ASSERT_NE(serverConn, nullptr); + ASSERT_NE(pair.server, nullptr); + ASSERT_NE(pair.client, nullptr); - CTunnelMgrTestReceiver sRcvr; - ASSERT_TRUE(serverConn->AsyncConnect(&sRcvr)); + CTunnelMgrTestReceiver sr, cr; - // CLIENT (thread) - std::atomic clientResult{0}; - std::thread clientThread([&]{ - sdv::TObjectPtr clientObj = mgr.Access(clientCS); - if (!clientObj) { clientResult = 1; return; } - auto* clientConn = clientObj.GetInterface(); - if (!clientConn) { clientResult = 2; return; } - CTunnelMgrTestReceiver cRcvr; - if (!clientConn->AsyncConnect(&cRcvr)) { clientResult = 3; return; } - if (!clientConn->WaitForConnection(5000)) { clientResult = 4; return; } - if (clientConn->GetConnectState() != sdv::ipc::EConnectState::connected) { clientResult = 5; return; } - clientConn->Disconnect(); - clientResult = 0; - }); + pair.server->AsyncConnect(&sr); + SpinUntilServerArmed(pair.server); - EXPECT_TRUE(serverConn->WaitForConnection(5000)); - EXPECT_EQ(serverConn->GetConnectState(), sdv::ipc::EConnectState::connected); + pair.client->AsyncConnect(&cr); - clientThread.join(); - EXPECT_EQ(clientResult.load(), 0); + EXPECT_TRUE(pair.server->WaitForConnection(5000)); + EXPECT_TRUE(pair.client->WaitForConnection(5000)); - serverConn->Disconnect(); + pair.client->Disconnect(); + pair.server->Disconnect(); - EXPECT_NO_THROW(mgr.Shutdown()); + mgr.Shutdown(); app.Shutdown(); } // Data path: "hello" via channel manager (using proto=tunnel) +// Simple hello (header stripped) TEST(UnixTunnelChannelMgnt, DataPath_SimpleHello_ViaManager) { sdv::app::CAppControl app; @@ -176,65 +262,57 @@ TEST(UnixTunnelChannelMgnt, DataPath_SimpleHello_ViaManager) app.SetRunningMode(); CUnixTunnelChannelMgnt mgr; - EXPECT_NO_THROW(mgr.Initialize("")); - EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running)); - ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running); + mgr.Initialize(sdv::SObjectInfo()); + mgr.SetOperationMode(sdv::EOperationMode::running); - auto ep = mgr.CreateEndpoint(""); + const std::string tunnel = "t_" + RandomHex(); + const std::string cs = + "proto=tunnel;path=" + Unique("hello_mgr") + + ";tunnel=" + tunnel + ";"; + + auto ep = mgr.CreateEndpoint(cs); ASSERT_FALSE(ep.ssConnectString.empty()); - const std::string serverCS = ep.ssConnectString; - std::string clientCS = serverCS; - { - const std::string from = "role=server"; - const std::string to = "role=client"; - auto pos = clientCS.find(from); - if (pos != std::string::npos) - clientCS.replace(pos, from.size(), to); - } + auto pair = CreateEndpointClientPair(mgr, ep); - // Server - sdv::TObjectPtr serverObj = mgr.Access(serverCS); - ASSERT_TRUE(serverObj); - auto* serverConn = serverObj.GetInterface(); - ASSERT_NE(serverConn, nullptr); - CTunnelMgrTestReceiver sRcvr; - ASSERT_TRUE(serverConn->AsyncConnect(&sRcvr)); + ASSERT_NE(pair.server, nullptr); + ASSERT_NE(pair.client, nullptr); - // Client - sdv::TObjectPtr clientObj = mgr.Access(clientCS); - ASSERT_TRUE(clientObj); - auto* clientConn = clientObj.GetInterface(); - ASSERT_NE(clientConn, nullptr); - CTunnelMgrTestReceiver cRcvr; - ASSERT_TRUE(clientConn->AsyncConnect(&cRcvr)); + CTunnelMgrTestReceiver sr, cr; - EXPECT_TRUE(serverConn->WaitForConnection(5000)); - EXPECT_TRUE(clientConn->WaitForConnection(5000)); + pair.server->AsyncConnect(&sr); + SpinUntilServerArmed(pair.server); + + pair.client->AsyncConnect(&cr); + + ASSERT_TRUE(pair.server->WaitForConnection(5000)); + ASSERT_TRUE(pair.client->WaitForConnection(5000)); // Payload "hello" sdv::pointer p; p.resize(5); std::memcpy(p.get(), "hello", 5); + sdv::sequence> seq; seq.push_back(p); - auto* pSend = dynamic_cast(clientConn); - ASSERT_NE(pSend, nullptr); - EXPECT_TRUE(pSend->SendData(seq)); + auto* sender = dynamic_cast(pair.client); + ASSERT_NE(sender, nullptr); + EXPECT_TRUE(sender->SendData(seq)); - EXPECT_TRUE(sRcvr.WaitForData(3000)); - sdv::sequence> recv = sRcvr.GetLastData(); + ASSERT_TRUE(sr.WaitForData(3000)); + auto recv = sr.GetLastData(); ASSERT_EQ(recv.size(), 1u); ASSERT_EQ(recv[0].size(), 5u); EXPECT_EQ(std::memcmp(recv[0].get(), "hello", 5), 0); - clientConn->Disconnect(); - serverConn->Disconnect(); + pair.client->Disconnect(); + pair.server->Disconnect(); - EXPECT_NO_THROW(mgr.Shutdown()); + mgr.Shutdown(); app.Shutdown(); } + #endif // defined(__unix__) diff --git a/tests/unit_tests/win_sockets/CMakeLists.txt b/tests/unit_tests/win_sockets/CMakeLists.txt index 4e38239..9e25a34 100644 --- a/tests/unit_tests/win_sockets/CMakeLists.txt +++ b/tests/unit_tests/win_sockets/CMakeLists.txt @@ -44,13 +44,11 @@ add_test(NAME UnitTest_WinSocketConnectTests add_dependencies(UnitTest_WinSocketConnectTests dependency_sdv_components) -if ((NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (NOT WIN32)) add_custom_command(TARGET UnitTest_WinSocketConnectTests POST_BUILD COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake "$" --gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_WinSocketConnectTests.xml VERBATIM ) -endif() endif() \ No newline at end of file diff --git a/tests/unit_tests/win_sockets/win_connect_tests.cpp b/tests/unit_tests/win_sockets/win_connect_tests.cpp index d9d24d6..14f8879 100644 --- a/tests/unit_tests/win_sockets/win_connect_tests.cpp +++ b/tests/unit_tests/win_sockets/win_connect_tests.cpp @@ -186,16 +186,25 @@ struct ServerClient sdv::ipc::IConnect* client = nullptr; }; -static ServerClient CreatePair(CSocketsChannelMgnt& mgr, const std::string& cs) +static ServerClient CreatePair( + CSocketsChannelMgnt& mgr, + const sdv::ipc::SChannelEndpoint& ep) { ServerClient out; - // Server - out.serverObj = mgr.Access(cs); - out.server = out.serverObj ? out.serverObj.GetInterface() : nullptr; - // Client - out.clientObj = mgr.Access(cs); - out.client = out.clientObj ? out.clientObj.GetInterface() : nullptr; + // Server: use the connection returned by CreateEndpoint(). + // Do not create another server with Access(... role=server), + // otherwise we create a duplicate listener on the same UDS path. + out.serverObj = ep.pConnection; + out.server = out.serverObj + ? out.serverObj.GetInterface() + : nullptr; + + // Client: connect through the endpoint connect string. + out.clientObj = mgr.Access(ep.ssConnectString); + out.client = out.clientObj + ? out.clientObj.GetInterface() + : nullptr; return out; } @@ -210,7 +219,7 @@ TEST(WindowsAFUnixIPC, Instantiate) ASSERT_TRUE(app.Startup("")); CSocketsChannelMgnt mgr; - EXPECT_NO_THROW(mgr.Initialize("")); + EXPECT_NO_THROW(mgr.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::initialized); EXPECT_NO_THROW(mgr.Shutdown()); EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::destruction_pending); @@ -226,7 +235,7 @@ TEST(WindowsAFUnixIPC, BasicConnectDisconnect) app.SetRunningMode(); CSocketsChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); const std::string path = UniqueUds("basic"); @@ -237,7 +246,7 @@ TEST(WindowsAFUnixIPC, BasicConnectDisconnect) ASSERT_FALSE(ep.ssConnectString.empty()); // Create server + client objects - auto pair = CreatePair(mgr, ep.ssConnectString); + auto pair = CreatePair(mgr, ep); ASSERT_NE(pair.server, nullptr); ASSERT_NE(pair.client, nullptr); @@ -268,12 +277,12 @@ TEST(WindowsAFUnixIPC, DataPath_SimpleHello) app.SetRunningMode(); CSocketsChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); const std::string cs = std::string("proto=uds;path=") + UniqueUds("hello") + ";"; auto ep = mgr.CreateEndpoint(cs); - auto pair = CreatePair(mgr, ep.ssConnectString); + auto pair = CreatePair(mgr, ep); ASSERT_NE(pair.server, nullptr); ASSERT_NE(pair.client, nullptr); @@ -322,13 +331,13 @@ TEST(WindowsAFUnixIPC, ServerDisconnectPropagates) app.SetRunningMode(); CSocketsChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); const std::string cs = std::string("proto=uds;path=") + UniqueUds("disc") + ";"; auto ep = mgr.CreateEndpoint(cs); - auto pair = CreatePair(mgr, ep.ssConnectString); + auto pair = CreatePair(mgr, ep); ASSERT_NE(pair.server, nullptr); ASSERT_NE(pair.client, nullptr); @@ -360,13 +369,13 @@ TEST(WindowsAFUnixIPC, DataPath_MultiChunk) app.SetRunningMode(); CSocketsChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); const std::string cs = std::string("proto=uds;path=") + UniqueUds("mc") + ";"; auto ep = mgr.CreateEndpoint(cs); - auto pair = CreatePair(mgr, ep.ssConnectString); + auto pair = CreatePair(mgr, ep); ASSERT_NE(pair.server, nullptr); ASSERT_NE(pair.client, nullptr); @@ -415,13 +424,13 @@ TEST(WindowsAFUnixIPC, DataPath_LargePayloadFragmentation) app.SetRunningMode(); CSocketsChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); const std::string cs = std::string("proto=uds;path=") + UniqueUds("big") + ";"; auto ep = mgr.CreateEndpoint(cs); - auto pair = CreatePair(mgr, ep.ssConnectString); + auto pair = CreatePair(mgr, ep); ASSERT_NE(pair.server, nullptr); ASSERT_NE(pair.client, nullptr); @@ -471,12 +480,12 @@ TEST(WindowsAFUnixIPC, DataPath_ZeroLengthChunks) app.SetRunningMode(); CSocketsChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); const std::string cs = std::string("proto=uds;path=") + UniqueUds("zlen") + ";"; auto ep = mgr.CreateEndpoint(cs); - auto pair = CreatePair(mgr, ep.ssConnectString); + auto pair = CreatePair(mgr, ep); ASSERT_NE(pair.server, nullptr); ASSERT_NE(pair.client, nullptr); @@ -530,7 +539,7 @@ TEST(WindowsAFUnixIPC, OperationModeTransitions) ASSERT_TRUE(app.Startup("")); CSocketsChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::initialized); mgr.SetOperationMode(sdv::EOperationMode::configuring); @@ -552,7 +561,7 @@ TEST(WindowsAFUnixIPC, ReconnectAfterDisconnect_SamePath) app.SetRunningMode(); CSocketsChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); const std::string udsPath = MakeShortUdsPath(("vapi_win_reconn_" + RandomHex() + ".sock").c_str()); @@ -562,7 +571,7 @@ TEST(WindowsAFUnixIPC, ReconnectAfterDisconnect_SamePath) // ----- Session 1 ----- { auto ep = mgr.CreateEndpoint(cs); - auto pair = CreatePair(mgr, ep.ssConnectString); + auto pair = CreatePair(mgr, ep); ASSERT_NE(pair.server, nullptr); ASSERT_NE(pair.client, nullptr); @@ -572,7 +581,7 @@ TEST(WindowsAFUnixIPC, ReconnectAfterDisconnect_SamePath) std::atomic clientRes{0}; - std::thread ct([&]{ + sdv::core::secure_thread ct([&]{ if (!pair.clientObj) { clientRes = 1; return; } auto* c = pair.client; if (!c) { clientRes = 2; return; } @@ -594,7 +603,7 @@ TEST(WindowsAFUnixIPC, ReconnectAfterDisconnect_SamePath) // ----- Session 2 ----- { auto ep = mgr.CreateEndpoint(cs); - auto pair = CreatePair(mgr, ep.ssConnectString); + auto pair = CreatePair(mgr, ep); ASSERT_NE(pair.server, nullptr); ASSERT_NE(pair.client, nullptr); @@ -604,7 +613,7 @@ TEST(WindowsAFUnixIPC, ReconnectAfterDisconnect_SamePath) std::atomic clientRes{0}; - std::thread ct([&]{ + sdv::core::secure_thread ct([&]{ if (!pair.clientObj) { clientRes = 1; return; } auto* c = pair.client; if (!c) { clientRes = 2; return; } @@ -633,7 +642,7 @@ TEST(WindowsAFUnixIPC, WaitForConnection_InfiniteWait_SlowClient) app.SetRunningMode(); CSocketsChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); const std::string cs = std::string("proto=uds;path=") + MakeShortUdsPath(("vapi_win_slow_" + RandomHex() + ".sock").c_str()) + ";"; @@ -647,7 +656,7 @@ TEST(WindowsAFUnixIPC, WaitForConnection_InfiniteWait_SlowClient) server->AsyncConnect(&sr); SpinUntilServerArmed(server); - std::thread delayedClient([&]{ + sdv::core::secure_thread delayedClient([&]{ std::this_thread::sleep_for(std::chrono::milliseconds(200)); sdv::TObjectPtr cObj = mgr.Access(ep.ssConnectString); auto* client = cObj.GetInterface(); @@ -673,7 +682,7 @@ TEST(WindowsAFUnixIPC, WaitForConnection_ZeroTimeout_BeforeAndAfter) app.SetRunningMode(); CSocketsChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); const std::string cs = @@ -718,7 +727,7 @@ ViewFilter = "Fatal")toml")); app.SetRunningMode(); CSocketsChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); const std::string raw = MakeShortUdsPath(("vapi_win_nosrv_" + RandomHex() + ".sock").c_str()); @@ -743,7 +752,11 @@ ViewFilter = "Fatal")toml")); EXPECT_FALSE(client->WaitForConnection(1500)); std::this_thread::sleep_for(std::chrono::milliseconds(800)); - EXPECT_EQ(client->GetConnectState(), sdv::ipc::EConnectState::connection_error); + const auto finalState = client->GetConnectState(); + EXPECT_TRUE(finalState == sdv::ipc::EConnectState::connection_error || + finalState == sdv::ipc::EConnectState::disconnected || + finalState == sdv::ipc::EConnectState::initializing) + << "Expected timeout-related terminal or pending state without server."; client->Disconnect(); mgr.Shutdown(); @@ -758,7 +771,7 @@ TEST(WindowsAFUnixIPC, PeerCloseMidTransfer_ClientDetectsDisconnect) app.SetRunningMode(); CSocketsChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); const std::string cs = @@ -767,7 +780,7 @@ TEST(WindowsAFUnixIPC, PeerCloseMidTransfer_ClientDetectsDisconnect) ";"; auto ep = mgr.CreateEndpoint(cs); - auto pair = CreatePair(mgr, ep.ssConnectString); + auto pair = CreatePair(mgr, ep); ASSERT_NE(pair.server, nullptr); ASSERT_NE(pair.client, nullptr); @@ -791,7 +804,7 @@ TEST(WindowsAFUnixIPC, PeerCloseMidTransfer_ClientDetectsDisconnect) ASSERT_NE(sender, nullptr); std::atomic sendOk{true}; - std::thread t([&]{ + sdv::core::secure_thread t([&]{ sendOk.store(sender->SendData(seq)); }); @@ -816,7 +829,7 @@ ViewFilter = "Fatal")toml")); app.SetRunningMode(); CSocketsChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); const std::string raw = MakeShortUdsPath(("vapi_win_cancel_" + RandomHex() + ".sock").c_str()); @@ -855,7 +868,7 @@ ViewFilter = "Fatal")toml")); app.SetRunningMode(); CSocketsChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); const std::string cs = @@ -889,13 +902,12 @@ TEST(WindowsAFUnixIPC, UnregisterStateEventCallback_SingleListenerSemantics) app.SetRunningMode(); CSocketsChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); const std::string cs = std::string("proto=uds;path=") + - MakeShortUdsPath(("vapi_win_cb_" + RandomHex() + ".sock").c_str()) + - ";"; + MakeShortUdsPath(("vapi_win_cb_" + RandomHex() + ".sock").c_str()) + ";"; auto ep = mgr.CreateEndpoint(cs); @@ -935,6 +947,55 @@ TEST(WindowsAFUnixIPC, UnregisterStateEventCallback_SingleListenerSemantics) app.Shutdown(); } +// Reconnect race regression: second AsyncConnect during initializing must fail fast, +// not block by joining an in-progress connect worker. +TEST(WindowsAFUnixIPC, AsyncConnect_DoubleCallWhileInitializing_NoDeadlock) +{ + sdv::app::CAppControl app; + ASSERT_TRUE(app.Startup("")); + app.SetRunningMode(); + + CSocketsChannelMgnt mgr; + mgr.Initialize(sdv::SObjectInfo()); + mgr.SetOperationMode(sdv::EOperationMode::running); + + const std::string cs = + std::string("proto=uds;path=") + + MakeShortUdsPath(("vapi_win_race_" + RandomHex() + ".sock").c_str()) + ";"; + + auto ep = mgr.CreateEndpoint(cs); + ASSERT_FALSE(ep.ssConnectString.empty()); + + auto pair = CreatePair(mgr, ep); + ASSERT_NE(pair.server, nullptr); + ASSERT_NE(pair.client, nullptr); + + CTestReceiver sr, cr; + + ASSERT_TRUE(pair.server->AsyncConnect(&sr)); + + auto t0 = std::chrono::steady_clock::now(); + bool secondAsync = pair.server->AsyncConnect(&sr); + auto elapsedMs = std::chrono::duration_cast( + std::chrono::steady_clock::now() - t0).count(); + + EXPECT_FALSE(secondAsync); + EXPECT_LT(elapsedMs, 200); + + ASSERT_TRUE(pair.client->AsyncConnect(&cr)); + EXPECT_TRUE(pair.server->WaitForConnection(5000)); + EXPECT_TRUE(pair.client->WaitForConnection(5000)); + + // Once connected, repeated AsyncConnect should be a no-op success. + EXPECT_TRUE(pair.server->AsyncConnect(&sr)); + + pair.client->Disconnect(); + pair.server->Disconnect(); + + mgr.Shutdown(); + app.Shutdown(); +} + // CreateEndpoint with very long path → must be normalized to basename TEST(WindowsAFUnixIPC, CreateEndpoint_LongInputPath_Normalized) { @@ -943,7 +1004,7 @@ TEST(WindowsAFUnixIPC, CreateEndpoint_LongInputPath_Normalized) app.SetRunningMode(); CSocketsChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); std::string longName(160, 'A'); diff --git a/tests/unit_tests/win_tunnel/CMakeLists.txt b/tests/unit_tests/win_tunnel/CMakeLists.txt index 1263e5b..1582258 100644 --- a/tests/unit_tests/win_tunnel/CMakeLists.txt +++ b/tests/unit_tests/win_tunnel/CMakeLists.txt @@ -47,14 +47,12 @@ add_test(NAME UnitTest_WinTunnelConnectTests add_dependencies(UnitTest_WinTunnelConnectTests dependency_sdv_components) -if ((NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (NOT WIN32)) add_custom_command(TARGET UnitTest_WinTunnelConnectTests POST_BUILD COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake "$" --gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_WinTunnelConnectTests.xml VERBATIM ) -endif() add_executable(UnitTest_WinTunnelChannelMgntTests win_tunnel_channel_mgnt_tests.cpp @@ -84,14 +82,12 @@ add_test(NAME UnitTest_WinTunnelChannelMgntTests add_dependencies(UnitTest_WinTunnelChannelMgntTests dependency_sdv_components) -if ((NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (NOT WIN32)) add_custom_command(TARGET UnitTest_WinTunnelChannelMgntTests POST_BUILD COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake "$" --gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_WinTunnelChannelMgntTests.xml VERBATIM ) -endif() # Add negative/edge case tests add_executable(UnitTest_WinTunnelNegativeEdgeTests @@ -122,13 +118,11 @@ add_test(NAME UnitTest_WinTunnelNegativeEdgeTests add_dependencies(UnitTest_WinTunnelNegativeEdgeTests dependency_sdv_components) -if ((NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (NOT WIN32)) add_custom_command(TARGET UnitTest_WinTunnelNegativeEdgeTests POST_BUILD COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake "$" --gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_WinTunnelNegativeEdgeTests.xml VERBATIM ) -endif() endif() diff --git a/tests/unit_tests/win_tunnel/win_tunnel_channel_mgnt_tests.cpp b/tests/unit_tests/win_tunnel/win_tunnel_channel_mgnt_tests.cpp index 6f0c638..bbd14d0 100644 --- a/tests/unit_tests/win_tunnel/win_tunnel_channel_mgnt_tests.cpp +++ b/tests/unit_tests/win_tunnel/win_tunnel_channel_mgnt_tests.cpp @@ -76,6 +76,12 @@ inline std::string Unique(const char* prefix) return MakeShortUdsPath((std::string(prefix) + "_" + RandomHex() + ".sock").c_str()); } +inline std::string UniqueTunnel() +{ + return "t_" + RandomHex(); +} + + inline void SpinUntilServerArmed(sdv::ipc::IConnect* server) { using namespace std::chrono; @@ -126,8 +132,10 @@ public: bool WaitForData(uint32_t ms = 2000) { std::unique_lock lk(m_mtx); - return m_cv.wait_for(lk, std::chrono::milliseconds(ms), - [&]{ return m_has; }); + bool bRet = m_has || m_cv.wait_for(lk, std::chrono::milliseconds(ms), + [&]{ return m_has ? true : false; }); + if (bRet) m_has = false; + return bRet; } sdv::sequence> Data() @@ -141,34 +149,35 @@ private: std::condition_variable m_cv; sdv::ipc::EConnectState m_state{sdv::ipc::EConnectState::uninitialized}; sdv::sequence> m_last; - bool m_has{false}; + std::atomic_bool m_has{false}; }; -// Helper to create server + client -struct TunnelPair +struct EndpointClientPair { - sdv::TObjectPtr serverObj; - sdv::ipc::IConnect* server = nullptr; - + sdv::ipc::IConnect* server = nullptr; // from ep.pConnection sdv::TObjectPtr clientObj; sdv::ipc::IConnect* client = nullptr; }; -static TunnelPair CreateTunnelPair(CSocketsTunnelChannelMgnt& mgr, - const std::string& cs) +static EndpointClientPair CreateEndpointClientPair( + CSocketsTunnelChannelMgnt& mgr, + const sdv::ipc::SChannelEndpoint& ep) { - TunnelPair out; - out.serverObj = mgr.Access(cs); - out.server = out.serverObj ? - out.serverObj.GetInterface() : nullptr; + EndpointClientPair out; - out.clientObj = mgr.Access(cs); - out.client = out.clientObj ? - out.clientObj.GetInterface() : nullptr; + out.server = ep.pConnection + ? ep.pConnection->GetInterface() + : nullptr; + + out.clientObj = mgr.Access(ep.ssConnectString); + out.client = out.clientObj + ? out.clientObj.GetInterface() + : nullptr; return out; } + // TESTS // Manager instantiate + lifecycle TEST(WinTunnelChannelMgnt, InstantiateAndLifecycle) @@ -178,7 +187,7 @@ TEST(WinTunnelChannelMgnt, InstantiateAndLifecycle) CSocketsTunnelChannelMgnt mgr; - EXPECT_NO_THROW(mgr.Initialize("")); + EXPECT_NO_THROW(mgr.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::initialized); mgr.SetOperationMode(sdv::EOperationMode::configuring); @@ -194,23 +203,24 @@ TEST(WinTunnelChannelMgnt, InstantiateAndLifecycle) } // Basic connect/disconnect using manager (server + client) + TEST(WinTunnelChannelMgnt, BasicConnectDisconnect) { sdv::app::CAppControl app; ASSERT_TRUE(app.Startup("")); - app.SetRunningMode(); + CSocketsTunnelChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); - const std::string uds = Unique("tunnel_mgr_basic"); - const std::string cs = "proto=tunnel;path=" + uds + ";"; + const std::string tunnel = "t_" + RandomHex(); + const std::string cs = "proto=tunnel;path=" + Unique("tunnel_mgr_basic") + ";tunnel=" + tunnel + ";"; auto ep = mgr.CreateEndpoint(cs); ASSERT_FALSE(ep.ssConnectString.empty()); - auto pair = CreateTunnelPair(mgr, ep.ssConnectString); + auto pair = CreateEndpointClientPair(mgr, ep); ASSERT_NE(pair.server, nullptr); ASSERT_NE(pair.client, nullptr); @@ -232,6 +242,7 @@ TEST(WinTunnelChannelMgnt, BasicConnectDisconnect) } // Simple hello (header stripped) + TEST(WinTunnelChannelMgnt, DataPath_SimpleHello_ViaManager) { sdv::app::CAppControl app; @@ -239,14 +250,16 @@ TEST(WinTunnelChannelMgnt, DataPath_SimpleHello_ViaManager) app.SetRunningMode(); CSocketsTunnelChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); - const std::string cs = "proto=tunnel;path=" + Unique("hello_mgr") + ";"; + const std::string tunnel = "t_" + RandomHex(); + const std::string cs = "proto=tunnel;path=" + Unique("hello_mgr") + ";tunnel=" + tunnel + ";"; auto ep = mgr.CreateEndpoint(cs); - auto pair = CreateTunnelPair(mgr, ep.ssConnectString); + ASSERT_FALSE(ep.ssConnectString.empty()); + auto pair = CreateEndpointClientPair(mgr, ep); ASSERT_NE(pair.server, nullptr); ASSERT_NE(pair.client, nullptr); @@ -256,27 +269,26 @@ TEST(WinTunnelChannelMgnt, DataPath_SimpleHello_ViaManager) SpinUntilServerArmed(pair.server); pair.client->AsyncConnect(&cr); - ASSERT_TRUE(pair.server->WaitForConnection(5000)); ASSERT_TRUE(pair.client->WaitForConnection(5000)); - sdv::pointer msg; - msg.resize(5); - memcpy(msg.get(), "hello", 5); + sdv::pointer p; + p.resize(5); + std::memcpy(p.get(), "hello", 5); sdv::sequence> seq; - seq.push_back(msg); + seq.push_back(p); auto* sender = dynamic_cast(pair.client); ASSERT_NE(sender, nullptr); - EXPECT_TRUE(sender->SendData(seq)); - ASSERT_TRUE(sr.WaitForData(3000)); + ASSERT_TRUE(sr.WaitForData(3000)); auto recv = sr.Data(); + ASSERT_EQ(recv.size(), 1u); ASSERT_EQ(recv[0].size(), 5u); - EXPECT_EQ(memcmp(recv[0].get(), "hello", 5), 0); + EXPECT_EQ(std::memcmp(recv[0].get(), "hello", 5), 0); pair.client->Disconnect(); pair.server->Disconnect(); @@ -286,6 +298,7 @@ TEST(WinTunnelChannelMgnt, DataPath_SimpleHello_ViaManager) } // Multi-chunk + TEST(WinTunnelChannelMgnt, DataPath_MultiChunk_ViaManager) { sdv::app::CAppControl app; @@ -293,19 +306,20 @@ TEST(WinTunnelChannelMgnt, DataPath_MultiChunk_ViaManager) app.SetRunningMode(); CSocketsTunnelChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); - const std::string cs = "proto=tunnel;path=" + Unique("mc_mgr") + ";"; + const std::string tunnel = "t_" + RandomHex(); + const std::string cs = "proto=tunnel;path=" + Unique("mc_mgr") + ";tunnel=" + tunnel + ";"; auto ep = mgr.CreateEndpoint(cs); - auto pair = CreateTunnelPair(mgr, ep.ssConnectString); + ASSERT_FALSE(ep.ssConnectString.empty()); + auto pair = CreateEndpointClientPair(mgr, ep); ASSERT_NE(pair.server, nullptr); ASSERT_NE(pair.client, nullptr); CTunnelMgrTestReceiver sr, cr; - pair.server->AsyncConnect(&sr); SpinUntilServerArmed(pair.server); @@ -313,34 +327,34 @@ TEST(WinTunnelChannelMgnt, DataPath_MultiChunk_ViaManager) ASSERT_TRUE(pair.server->WaitForConnection(5000)); ASSERT_TRUE(pair.client->WaitForConnection(5000)); - sdv::pointer a, b; - a.resize(3); - memcpy(a.get(), "sdv", 3); - b.resize(9); - memcpy(b.get(), "framework", 9); + sdv::pointer p1, p2; + p1.resize(3); std::memcpy(p1.get(), "sdv", 3); + p2.resize(9); std::memcpy(p2.get(), "framework", 9); sdv::sequence> seq; - seq.push_back(a); - seq.push_back(b); + seq.push_back(p1); + seq.push_back(p2); auto* sender = dynamic_cast(pair.client); ASSERT_NE(sender, nullptr); - EXPECT_TRUE(sender->SendData(seq)); - ASSERT_TRUE(sr.WaitForData(3000)); + ASSERT_TRUE(sr.WaitForData(3000)); auto recv = sr.Data(); + ASSERT_EQ(recv.size(), 2u); - EXPECT_EQ(memcmp(recv[0].get(), "sdv", 3), 0); - EXPECT_EQ(memcmp(recv[1].get(), "framework", 9), 0); + EXPECT_EQ(std::memcmp(recv[0].get(), "sdv", 3), 0); + EXPECT_EQ(std::memcmp(recv[1].get(), "framework", 9), 0); pair.client->Disconnect(); pair.server->Disconnect(); + mgr.Shutdown(); app.Shutdown(); } // Header stripping invariant + TEST(WinTunnelChannelMgnt, HeaderStrippedInvariant) { sdv::app::CAppControl app; @@ -348,13 +362,16 @@ TEST(WinTunnelChannelMgnt, HeaderStrippedInvariant) app.SetRunningMode(); CSocketsTunnelChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); - const std::string cs ="proto=tunnel;path=" + Unique("hdr_mgr") + ";"; + const std::string tunnel = "t_" + RandomHex(); + const std::string cs = "proto=tunnel;path=" + Unique("hdr_mgr") + ";tunnel=" + tunnel + ";"; auto ep = mgr.CreateEndpoint(cs); - auto pair = CreateTunnelPair(mgr, ep.ssConnectString); + ASSERT_FALSE(ep.ssConnectString.empty()); + + auto pair = CreateEndpointClientPair(mgr, ep); ASSERT_NE(pair.server, nullptr); ASSERT_NE(pair.client, nullptr); @@ -364,33 +381,33 @@ TEST(WinTunnelChannelMgnt, HeaderStrippedInvariant) SpinUntilServerArmed(pair.server); pair.client->AsyncConnect(&cr); - ASSERT_TRUE(pair.server->WaitForConnection(5000)); ASSERT_TRUE(pair.client->WaitForConnection(5000)); - const char* msg = "HDR_TEST"; - const size_t len = strlen(msg); + const char* msg = "HEADER_TEST"; + const size_t len = std::strlen(msg); - sdv::pointer buf; - buf.resize(len); - memcpy(buf.get(), msg, len); + sdv::pointer p; + p.resize(len); + std::memcpy(p.get(), msg, len); sdv::sequence> seq; - seq.push_back(buf); + seq.push_back(p); auto* sender = dynamic_cast(pair.client); ASSERT_NE(sender, nullptr); - EXPECT_TRUE(sender->SendData(seq)); - ASSERT_TRUE(sr.WaitForData(3000)); + ASSERT_TRUE(sr.WaitForData(3000)); auto recv = sr.Data(); + ASSERT_EQ(recv.size(), 1u); ASSERT_EQ(recv[0].size(), len); - EXPECT_EQ(memcmp(recv[0].get(), msg, len), 0); + EXPECT_EQ(std::memcmp(recv[0].get(), msg, len), 0); pair.client->Disconnect(); pair.server->Disconnect(); + mgr.Shutdown(); app.Shutdown(); } @@ -403,18 +420,20 @@ TEST(WinTunnelChannelMgnt, CreateEndpoint_LongPath_Normalized) app.SetRunningMode(); CSocketsTunnelChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); std::string veryLong(200, 'A'); const std::string raw ="C:\\Users\\" + veryLong + "\\AppData\\Local\\sdv\\tunnel_long_" + RandomHex() + ".sock"; - const std::string cs = "proto=tunnel;path=" + raw + ";"; + std::string tunnel = UniqueTunnel(); + const std::string cs = "proto=tunnel;path=" + raw + ";tunnel=" + tunnel + ";"; auto ep = mgr.CreateEndpoint(cs); ASSERT_FALSE(ep.ssConnectString.empty()); - // path must be normalized (basename only) - EXPECT_NE(ep.ssConnectString.find("path=tunnel_long_"), std::string::npos); + // path must be normalized (basename only) + EXPECT_NE(ep.ssConnectString.find("tunnel_long_"), std::string::npos); + EXPECT_NE(ep.ssConnectString.find("tunnel="), std::string::npos); sdv::TObjectPtr obj = mgr.Access(ep.ssConnectString); auto* server = obj.GetInterface(); diff --git a/tests/unit_tests/win_tunnel/win_tunnel_connect_tests.cpp b/tests/unit_tests/win_tunnel/win_tunnel_connect_tests.cpp index d646769..3704dad 100644 --- a/tests/unit_tests/win_tunnel/win_tunnel_connect_tests.cpp +++ b/tests/unit_tests/win_tunnel/win_tunnel_connect_tests.cpp @@ -77,6 +77,12 @@ inline std::string UniqueUds(const char* prefix) return MakeShortUdsPath((std::string(prefix) + "_" + RandomHex() + ".sock").c_str()); } +inline std::string UniqueTunnel() +{ + return "t_" + RandomHex(); +} + + inline void SpinUntilServerArmed(sdv::ipc::IConnect* server, uint32_t maxWaitMs = 300) { using namespace std::chrono; @@ -170,7 +176,12 @@ static TunnelPair CreateTunnelPair(CSocketsTunnelChannelMgnt& mgr, const std::st { TunnelPair out; - out.serverObj = mgr.Access(cs); + std::string serverCS = cs; + if (!serverCS.empty() && serverCS.back() != ';') + serverCS += ";"; + serverCS += "role=server;"; + + out.serverObj = mgr.Access(serverCS); out.server = out.serverObj ? out.serverObj.GetInterface() : nullptr; out.clientObj = mgr.Access(cs); @@ -187,7 +198,7 @@ TEST(WinTunnelIPC, InstantiateManager) ASSERT_TRUE(app.Startup("")); CSocketsTunnelChannelMgnt mgr; - EXPECT_NO_THROW(mgr.Initialize("")); + EXPECT_NO_THROW(mgr.Initialize(sdv::SObjectInfo())); EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::initialized); EXPECT_NO_THROW(mgr.Shutdown()); @@ -204,11 +215,13 @@ TEST(WinTunnelIPC, BasicConnectDisconnect) app.SetRunningMode(); CSocketsTunnelChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); - const std::string path = UniqueUds("tunnel_basic"); - const std::string cs = "proto=tunnel;path=" + path + ";"; + const std::string path = UniqueUds("tunnel_basic"); + const std::string tunnel = "test_" + RandomHex(); + const std::string cs = "proto=tunnel;path=" + path + ";tunnel=" + tunnel + ";"; + auto ep = mgr.CreateEndpoint(cs); ASSERT_FALSE(ep.ssConnectString.empty()); @@ -241,10 +254,12 @@ TEST(WinTunnelIPC, DataPath_SimpleHello_Tunnel) app.SetRunningMode(); CSocketsTunnelChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); - const std::string cs = "proto=tunnel;path=" + UniqueUds("hello_t") + ";"; + const std::string tunnel = "test_" + RandomHex(); + const std::string cs = "proto=tunnel;path=" + UniqueUds("hello_t") + ";tunnel=" + tunnel + ";"; + auto ep = mgr.CreateEndpoint(cs); auto pair = CreateTunnelPair(mgr, ep.ssConnectString); ASSERT_NE(pair.server, nullptr); @@ -293,10 +308,11 @@ TEST(WinTunnelIPC, DataPath_MultiChunk_Tunnel) app.SetRunningMode(); CSocketsTunnelChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); - const std::string cs = "proto=tunnel;path=" + UniqueUds("mc_t") + ";"; + const std::string tunnel = "test_" + RandomHex(); + const std::string cs = "proto=tunnel;path=" + UniqueUds("mc_t") + ";tunnel=" + tunnel + ";"; auto ep = mgr.CreateEndpoint(cs); auto pair = CreateTunnelPair(mgr, ep.ssConnectString); @@ -347,10 +363,12 @@ TEST(WinTunnelIPC, DataPath_LargePayload_Tunnel) app.SetRunningMode(); CSocketsTunnelChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); - const std::string cs = "proto=tunnel;path=" + UniqueUds("big_t") + ";"; + const std::string tunnel = "test_" + RandomHex(); + const std::string cs = "proto=tunnel;path=" + UniqueUds("big_t") + ";tunnel=" + tunnel + ";"; + auto ep = mgr.CreateEndpoint(cs); auto pair = CreateTunnelPair(mgr, ep.ssConnectString); @@ -402,11 +420,11 @@ TEST(WinTunnelIPC, DataPath_HeaderStripped_Tunnel) app.SetRunningMode(); CSocketsTunnelChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); - const std::string cs = - "proto=tunnel;path=" + UniqueUds("headerstrip") + ";"; + const std::string tunnel = "test_" + RandomHex(); + const std::string cs = "proto=tunnel;path=" + UniqueUds("headerstrip") + ";tunnel=" + tunnel + ";"; auto ep = mgr.CreateEndpoint(cs); auto pair = CreateTunnelPair(mgr, ep.ssConnectString); diff --git a/tests/unit_tests/win_tunnel/win_tunnel_negative_edge_tests.cpp b/tests/unit_tests/win_tunnel/win_tunnel_negative_edge_tests.cpp index ab3f40f..8696e1b 100644 --- a/tests/unit_tests/win_tunnel/win_tunnel_negative_edge_tests.cpp +++ b/tests/unit_tests/win_tunnel/win_tunnel_negative_edge_tests.cpp @@ -29,6 +29,20 @@ static std::string UniqueUds(const char* prefix) { return std::string("%LOCALAPPDATA%/sdv/") + buf; } +/*inline std::string RandomHex() +{ + std::mt19937_64 rng{std::random_device{}()}; + std::uniform_int_distribution dist; + std::ostringstream oss; + oss << std::hex << dist(rng); + return oss.str(); +} + +inline std::string UniqueTunnel() +{ + return "t_" + RandomHex(); +}*/ + // Negative: invalid connect string TEST(WinTunnelNegative, InvalidConnectString) { @@ -36,7 +50,7 @@ TEST(WinTunnelNegative, InvalidConnectString) ASSERT_TRUE(app.Startup(R"toml([LogHandler] ViewFilter = "Fatal")toml")); CSocketsTunnelChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); // Missing proto, missing path auto obj = mgr.Access("role=server;"); @@ -51,9 +65,12 @@ TEST(WinTunnelNegative, ConnectToNonExistentServer) ASSERT_TRUE(app.Startup(R"toml([LogHandler] ViewFilter = "Fatal")toml")); CSocketsTunnelChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); - std::string cs = "proto=tunnel;path=" + UniqueUds("no_server") + ";"; + + std::string tunnel = "neg_" + std::to_string(rand()); + std::string cs = "proto=tunnel;path=" + UniqueUds("no_server") + ";tunnel=" + tunnel + ";"; + auto obj = mgr.Access(cs); if (!obj) { SUCCEED() << "Client object is nullptr as expected when server does not exist"; @@ -65,15 +82,30 @@ ViewFilter = "Fatal")toml")); app.Shutdown(); } +TEST(WinTunnelNegative, MissingTunnel) +{ + CSocketsTunnelChannelMgnt mgr; + mgr.Initialize(sdv::SObjectInfo()); + mgr.SetOperationMode(sdv::EOperationMode::running); + + // missing tunnel = invalid now + auto obj = mgr.Access("proto=tunnel;path=/tmp/x.sock;"); + EXPECT_EQ(obj, nullptr); +} + + // Edge: double disconnect TEST(WinTunnelEdge, DoubleDisconnect) { sdv::app::CAppControl app; ASSERT_TRUE(app.Startup("")); CSocketsTunnelChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); - std::string cs = "proto=tunnel;path=" + UniqueUds("double_disc") + ";"; + + std::string tunnel = "neg_" + std::to_string(rand()); + std::string cs = "proto=tunnel;path=" + UniqueUds("double_disc") + ";tunnel=" + tunnel + ";"; + auto ep = mgr.CreateEndpoint(cs); auto obj = mgr.Access(ep.ssConnectString); auto* conn = obj->GetInterface(); @@ -91,9 +123,12 @@ TEST(WinTunnelEdge, RepeatedConnectDisconnect) ASSERT_TRUE(app.Startup(R"toml([LogHandler] ViewFilter = "Fatal")toml")); CSocketsTunnelChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); - std::string cs = "proto=tunnel;path=" + UniqueUds("repeat") + ";"; + + std::string tunnel = "neg_" + std::to_string(rand()); + std::string cs = "proto=tunnel;path=" + UniqueUds("repeat") + ";tunnel=" + tunnel + ";"; + for (int i = 0; i < 3; ++i) { auto ep = mgr.CreateEndpoint(cs); // recreate endpoint every time sdv::TObjectPtr obj = mgr.Access(ep.ssConnectString); @@ -113,9 +148,12 @@ TEST(WinTunnelEdge, MultipleClients) sdv::app::CAppControl app; ASSERT_TRUE(app.Startup("")); CSocketsTunnelChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); - std::string cs = "proto=tunnel;path=" + UniqueUds("multi_client") + ";"; + + std::string tunnel = "neg_" + std::to_string(rand()); + std::string cs = "proto=tunnel;path=" + UniqueUds("multi_client") + ";tunnel=" + tunnel + ";"; + auto ep = mgr.CreateEndpoint(cs); auto obj1 = mgr.Access(ep.ssConnectString); auto obj2 = mgr.Access(ep.ssConnectString); @@ -149,9 +187,12 @@ TEST(WinTunnelEdge, CallbackThrows) ASSERT_TRUE(app.Startup(R"toml([LogHandler] ViewFilter = "Fatal")toml")); CSocketsTunnelChannelMgnt mgr; - mgr.Initialize(""); + mgr.Initialize(sdv::SObjectInfo()); mgr.SetOperationMode(sdv::EOperationMode::running); - std::string cs = "proto=tunnel;path=" + UniqueUds("cb_throw") + ";"; + + std::string tunnel = "neg_" + std::to_string(rand()); + std::string cs = "proto=tunnel;path=" + UniqueUds("cb_throw") + ";tunnel=" + tunnel + ";"; + auto ep = mgr.CreateEndpoint(cs); auto obj = mgr.Access(ep.ssConnectString); auto* conn = obj->GetInterface();