diff --git a/.github/workflows/config/ubsan.meta b/.github/workflows/config/ubsan.meta new file mode 100644 index 00000000000..336a5cdb615 --- /dev/null +++ b/.github/workflows/config/ubsan.meta @@ -0,0 +1,17 @@ +names: + fastdds: + cmake-args: + - -DNO_TLS=OFF + - -DSECURITY=ON + - -DFASTDDS_STATISTICS=ON + - -DSANITIZER=Undefined + - -DCOMPILE_WARNING_AS_ERROR=ON + - -DEPROSIMA_BUILD_TESTS=ON + - -DRTPS_API_TESTS=ON + - -DFASTDDS_PIM_API_TESTS=ON + - -DPERFORMANCE_TESTS=ON + googletest-distribution: + cmake-args: + - -Dgtest_force_shared_crt=ON + - -DBUILD_SHARED_LIBS=ON + - -DBUILD_GMOCK=ON diff --git a/.github/workflows/reusable-sanitizers-ci.yml b/.github/workflows/reusable-sanitizers-ci.yml index 408fbc713b5..0183bbdb886 100644 --- a/.github/workflows/reusable-sanitizers-ci.yml +++ b/.github/workflows/reusable-sanitizers-ci.yml @@ -19,6 +19,10 @@ on: description: 'Run Thread Sanitizer job for Fast DDS' required: false type: boolean + run_ubsan_fastdds: + description: 'Run Undefined Behavior Sanitizer job for Fast DDS' + required: false + type: boolean colcon_build_args: description: 'Optional colcon build arguments' required: false @@ -492,3 +496,189 @@ jobs: run: | Write-Host ${{ steps.report_summary.outcome }} exit 1 + + ubsan_fastdds_build: + if: ${{ inputs.run_ubsan_fastdds == true }} + runs-on: ubuntu-24.04 + steps: + - name: Free disk space + uses: eProsima/eProsima-CI/ubuntu/free_disk_space@v0 + + - name: Sync eProsima/Fast-DDS repository + uses: eProsima/eProsima-CI/external/checkout@v0 + with: + path: src/fastdds + submodules: true + ref: ${{ inputs.fastdds_ref }} + + - name: Install Fix Python version + uses: eProsima/eProsima-CI/external/setup-python@v0 + with: + python-version: '3.11' + + - name: Get minimum supported version of CMake + uses: eProsima/eProsima-CI/external/get-cmake@v0 + with: + cmakeVersion: '3.28.6' + + - name: Install apt packages + uses: eProsima/eProsima-CI/ubuntu/install_apt_packages@v0 + with: + packages: curl grep libasio-dev libtinyxml2-dev python3 python3-pip software-properties-common wget + + - name: Install colcon + uses: eProsima/eProsima-CI/ubuntu/install_colcon@v0 + + - name: Install Python dependencies + uses: eProsima/eProsima-CI/ubuntu/install_python_packages@v0 + with: + packages: vcstool xmlschema + + - name: Setup CCache + uses: eProsima/eProsima-CI/external/setup-ccache-action@v0 + if: ${{ !always() }} + with: + api_token: ${{ secrets.GITHUB_TOKEN }} + + - name: Get Fast CDR branch + id: get_fastcdr_branch + uses: eProsima/eProsima-CI/ubuntu/get_related_branch_from_repo@v0 + with: + remote_repository: eProsima/Fast-CDR + fallback_branch: ${{ inputs.fastcdr_ref }} + + - name: Download Fast CDR + uses: eProsima/eProsima-CI/external/checkout@v0 + with: + repository: eProsima/Fast-CDR + path: ${{ github.workspace }}/src/fastcdr + ref: ${{ steps.get_fastcdr_branch.outputs.deduced_branch }} + + - name: Fetch Fast DDS dependencies + uses: eProsima/eProsima-CI/ubuntu/vcs_import@v0 + with: + vcs_repos_file: ${{ github.workspace }}/src/fastdds/fastdds.repos + destination_workspace: src + skip_existing: 'true' + + - name: Fetch Fast DDS CI dependencies + uses: eProsima/eProsima-CI/ubuntu/vcs_import@v0 + with: + vcs_repos_file: ${{ github.workspace }}/src/fastdds/.github/workflows/config/fastdds_test.repos + destination_workspace: src + skip_existing: 'true' + + - name: Show .meta file + id: show_meta + run: | + cat ${{ github.workspace }}/src/fastdds/.github/workflows/config/ubsan.meta + + - name: Colcon build + continue-on-error: false + uses: eProsima/eProsima-CI/ubuntu/colcon_build@v0 + with: + colcon_meta_file: ${{ github.workspace }}/src/fastdds/.github/workflows/config/ubsan.meta + colcon_build_args: ${{ inputs.colcon_build_args }} + cmake_args: ${{ inputs.cmake_args }} + cmake_args_default: '' + cmake_build_type: 'Debug' + workspace: ${{ github.workspace }} + workspace_dependencies: '' + + - name: Pack workspace preserving permissions + shell: bash + run: | + tar -C "${{ github.workspace }}" -cpf "${{ runner.temp }}/ubsan_build_${{ inputs.label }}.tar" . + + - name: Upload build artifacts + uses: eProsima/eProsima-CI/external/upload-artifact@v0 + with: + name: build_artifacts_fastdds_ubsan_${{ inputs.label }} + path: ${{ runner.temp }}/ubsan_build_${{ inputs.label }}.tar + + ubsan_fastdds_test: + if: ${{ inputs.run_ubsan_fastdds == true }} + needs: ubsan_fastdds_build + runs-on: ubuntu-24.04 + env: + UBSAN_OPTIONS: halt_on_error=1:abort_on_error=1:print_stacktrace=1 + steps: + - name: Free disk space + uses: eProsima/eProsima-CI/ubuntu/free_disk_space@v0 + + - name: Download build artifact + uses: eProsima/eProsima-CI/external/download-artifact@v0 + with: + name: build_artifacts_fastdds_ubsan_${{ inputs.label }} + path: ${{ runner.temp }} + + - name: Unpack workspace preserving permissions + shell: bash + run: | + tar -C "${{ github.workspace }}" -xpf "${{ runner.temp }}/ubsan_build_${{ inputs.label }}.tar" + + - name: Install Fix Python version + uses: eProsima/eProsima-CI/external/setup-python@v0 + with: + python-version: '3.11' + + - name: Get minimum supported version of CMake + uses: eProsima/eProsima-CI/external/get-cmake@v0 + with: + cmakeVersion: '3.28.6' + + - name: Install apt packages + uses: eProsima/eProsima-CI/ubuntu/install_apt_packages@v0 + with: + packages: curl grep libasio-dev libtinyxml2-dev python3 python3-pip software-properties-common wget + + - name: Install colcon + uses: eProsima/eProsima-CI/ubuntu/install_colcon@v0 + + - name: Install Python dependencies + uses: eProsima/eProsima-CI/ubuntu/install_python_packages@v0 + with: + packages: setuptools gcovr tomark xmltodict jsondiff pandas psutil + + - name: Set up hosts file for DNS testing + run: | + sudo echo "" | sudo tee -a /etc/hosts + sudo echo "127.0.0.1 localhost.test" | sudo tee -a /etc/hosts + sudo echo "::1 localhost.test" | sudo tee -a /etc/hosts + sudo echo "154.56.134.194 www.eprosima.com.test" | sudo tee -a /etc/hosts + sudo echo "216.58.215.164 www.acme.com.test" | sudo tee -a /etc/hosts + sudo echo "2a00:1450:400e:803::2004 www.acme.com.test" | sudo tee -a /etc/hosts + sudo echo "140.82.121.4 www.foo.com.test" | sudo tee -a /etc/hosts + sudo echo "140.82.121.3 www.foo.com.test" | sudo tee -a /etc/hosts + sudo echo "ff1e::ffff:efff:1 acme.org.test" | sudo tee -a /etc/hosts + + - name: Colcon test + id: test + continue-on-error: true + uses: eProsima/eProsima-CI/ubuntu/colcon_test@v0 + with: + colcon_args_default: '' + colcon_test_args: ${{ inputs.colcon_test_args }} + colcon_test_args_default: '--event-handlers=console_direct+ --return-code-on-test-failure' + ctest_args: ${{ inputs.ctest_args }} + ctest_args_default: '--timeout 300 --label-exclude "xfail"' + packages_names: fastdds + workspace: ${{ github.workspace }} + workspace_dependencies: '' + test_report_artifact: ${{ format('test_report_{0}_{1}', inputs.label, github.job) }} + + - name: Report sanitizer errors + run: | + # UBSan finding header lines look like: + # ::: runtime error: + # Filter on ": runtime error:" so we keep one line per finding (the header) + bash src/fastdds/.github/workflows/utils/specific_errors_filter.sh \ + ": runtime error:" \ + log/latest_test/fastdds/stdout_stderr.log \ + _tmp_specific_error_file.log + + python3 src/fastdds/.github/workflows/utils/log_parser.py \ + --log-file log/latest_test/fastdds/stdout_stderr.log \ + --specific-error-file _tmp_specific_error_file.log \ + --output-file $GITHUB_STEP_SUMMARY \ + --sanitizer ubsan diff --git a/.github/workflows/sanitizers-ci.yml b/.github/workflows/sanitizers-ci.yml index e8d79a533bd..ac2aec24ad9 100644 --- a/.github/workflows/sanitizers-ci.yml +++ b/.github/workflows/sanitizers-ci.yml @@ -19,6 +19,10 @@ on: description: 'Run Thread Sanitizer job for Fast DDS' required: false type: boolean + run_ubsan_fastdds: + description: 'Run Undefined Behavior Sanitizer job for Fast DDS' + required: false + type: boolean colcon_build_args: description: 'Optional colcon build arguments' required: false @@ -69,6 +73,7 @@ jobs: run_asan_fastdds: ${{ ((inputs.run_asan_fastdds == true) && true) || github.event_name == 'pull_request' }} run_asan_discovery_server: ${{ ((inputs.run_asan_discovery_server == true) && true) || github.event_name == 'pull_request' }} run_tsan_fastdds: ${{ ((inputs.run_tsan_fastdds == true) && true) || github.event_name == 'pull_request' }} + run_ubsan_fastdds: ${{ ((inputs.run_ubsan_fastdds == true) && true) || github.event_name == 'pull_request' }} colcon_build_args: ${{ inputs.colcon_build_args || '' }} colcon_test_args: ${{ inputs.colcon_test_args || '' }} cmake_args: ${{ inputs.cmake_args || '' }} diff --git a/.github/workflows/utils/log_parser.py b/.github/workflows/utils/log_parser.py index 937f71635da..f4ade3573e3 100644 --- a/.github/workflows/utils/log_parser.py +++ b/.github/workflows/utils/log_parser.py @@ -22,6 +22,23 @@ DESCRIPTION = """Script to read a test log and return a summary table""" USAGE = ('python3 log_parser.py') +# Each UBSan finding starts with a source location followed by " runtime error: ". +# We group findings by "::: " so that the same UB at the +# same site collapses into a single row regardless of repetition or ctest line prefixes +_UBSAN_LINE_RE = re.compile( + r'(?P\S+?):(?P\d+):(?P\d+):\s*runtime error:\s*(?P.+?)\s*$' +) +# Strip GitHub Actions ISO-8601 timestamp prefix and/or a ctest line prefix (": "). +# The ctest prefix captures the test id so we can attribute UBSan findings to their test; +# .sub() still strips just the prefix, and .match() exposes the id via the named group. +_GH_TIMESTAMP_RE = re.compile(r'^\d{4}-\d{2}-\d{2}T\S+\s+') +_CTEST_PREFIX_RE = re.compile(r'^(?P\d+):\s*') +# Hex addresses ("0x55ade3179480") differ across runs but represent the same logical UB; +# normalize them to "0x..." so identical findings at the same site collapse into one row. +_HEX_ADDRESS_RE = re.compile(r'0x[0-9a-fA-F]+') +# Path fragments whose UBSan findings are suppressed (thirdparty code we don't maintain). +_UBSAN_IGNORED_PATH_SUBSTRINGS = ('sqlite3.c',) + def parse_options(): """ @@ -49,7 +66,7 @@ def parse_options(): '--specific-error-file', type=str, required=True, - help='Path to file with ASAN or TSAN specific errors.' + help='Path to file with ASAN, TSAN or UBSAN specific errors.' ) required_args.add_argument( '-o', @@ -64,14 +81,17 @@ def parse_options(): '--sanitizer', type=str, required=True, - help='Sanitizer to use. [ASAN|TSAN] no case sensitive.' + help='Sanitizer to use. [ASAN|TSAN|UBSAN] no case sensitive.' ) return parser.parse_args() def failure_test_list( - log_file_path: str): + log_file_path: str, + ignored_test_ids: set = None): + + ignored_test_ids = ignored_test_ids or set() # failed tests saved_lines = [] @@ -93,12 +113,16 @@ def failure_test_list( # Thus, it uses this variable to get the correct # of spaces in error name n_spaces_in_error = len(test[test.find('(')+1:test.find(')')].split()) - 1 + test_id = split_test[- 4 - n_spaces_in_error] + if test_id in ignored_test_ids: + continue + # Insert as failed test (in first place, because we are reading them inversed) # the new test failed with the name and id taken from split line failed_tests.insert( 0, dict({ - 'ID': split_test[- 4 - n_spaces_in_error], + 'ID': test_id, 'Name': split_test[- 2 - n_spaces_in_error], 'Type': test[test.find('(')+1:test.find(')')] })) @@ -117,6 +141,35 @@ def failure_test_list( return failed_tests, n_errors +def tests_failing_only_in_ignored_ubsan_paths( + log_file_path: str, + ignored_path_substrings: tuple): + """ + Return the set of ctest test ids whose UBSan findings are exclusively in files + matching ignored_path_substrings. Tests with no UBSan findings or with at least + one finding outside the ignored paths are excluded (i.e., they remain reported). + """ + test_id_to_paths = {} + with open(log_file_path, 'r', encoding='utf-8', errors='replace') as file: + for line in file: + stripped = _GH_TIMESTAMP_RE.sub('', line.rstrip('\r\n')) + id_match = _CTEST_PREFIX_RE.match(stripped) + if not id_match: + continue + ubsan_match = _UBSAN_LINE_RE.search(stripped[id_match.end():]) + if not ubsan_match: + continue + test_id_to_paths.setdefault(id_match.group('id'), set()).add( + ubsan_match.group('path')) + + return { + test_id for test_id, paths in test_id_to_paths.items() + if paths and all( + any(sub in path for sub in ignored_path_substrings) + for path in paths) + } + + def _common_line_splitter( line: str, text_to_split_start: str, @@ -148,6 +201,22 @@ def tsan_line_splitter( text_to_split_end=' (pid=') +def ubsan_line_splitter( + line: str): + stripped = _GH_TIMESTAMP_RE.sub('', line.rstrip('\r\n')) + stripped = _CTEST_PREFIX_RE.sub('', stripped) + match = _UBSAN_LINE_RE.search(stripped) + if not match: + # Not an UBSan finding header + return None + if any(sub in match.group('path') for sub in _UBSAN_IGNORED_PATH_SUBSTRINGS): + # Ignore UBSan findings in thirdparty code we don't maintain. + return None + basename = match.group('path').rsplit('/', 1)[-1] + desc = _HEX_ADDRESS_RE.sub('0x...', match.group('desc')) + return f"{basename}:{match.group('line')}:{match.group('col')}: {desc}" + + def common_specific_errors_list( errors_file_path: str, line_splitter): @@ -170,11 +239,14 @@ def common_specific_errors_dict( errors_file_path: str, line_splitter): - # failed tests + # Open with newline='' so Python's universal-newline mode does not split a single + # grep-emitted line on bare '\r' characters. errors = {} - with open(errors_file_path, 'r') as file: - for line in file.readlines(): + with open(errors_file_path, 'r', newline='') as file: + for line in file: error_id = line_splitter(line) + if error_id is None: + continue if error_id in errors: errors[error_id] += 1 else: @@ -203,10 +275,19 @@ def main(): # Parse arguments args = parse_options() - # Get specific ASAN or TSAN variables - asan = args.sanitizer.lower() == 'asan' - line_splitter = (asan_line_splitter if asan else tsan_line_splitter) - file_title = ('ASAN' if asan else 'TSAN') + ' Errors Summary' + # Select the splitter and report title for the requested sanitizer. + sanitizer = args.sanitizer.lower() + splitters = { + 'asan': (asan_line_splitter, 'ASAN'), + 'tsan': (tsan_line_splitter, 'TSAN'), + 'ubsan': (ubsan_line_splitter, 'UBSAN'), + } + if sanitizer not in splitters: + raise SystemExit( + f"Unsupported sanitizer '{args.sanitizer}'. Expected one of: " + f"{', '.join(s.upper() for s in splitters)}.") + line_splitter, title_prefix = splitters[sanitizer] + file_title = f'{title_prefix} Errors Summary' # Execute specific errors parse specific_errors, n_errors = common_specific_errors_list( @@ -217,9 +298,18 @@ def main(): result=specific_errors, output_file_path=args.output_file) + # For UBSan, also drop from the "Tests failed" table any test whose findings + # were all in ignored thirdparty paths (matches what we suppress above). + ignored_test_ids = set() + if sanitizer == 'ubsan': + ignored_test_ids = tests_failing_only_in_ignored_ubsan_paths( + log_file_path=args.log_file, + ignored_path_substrings=_UBSAN_IGNORED_PATH_SUBSTRINGS) + # Execute failed tests tests_failed, _ = failure_test_list( - log_file_path=args.log_file) + log_file_path=args.log_file, + ignored_test_ids=ignored_test_ids) print_list_to_markdown( title='Tests failed', result=tests_failed, diff --git a/CMakeLists.txt b/CMakeLists.txt index c051b87f1d2..671963dbefe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,6 +107,94 @@ check_compile_feature() check_endianness() ############################################################################### +<<<<<<< HEAD +======= +# Activate Sanitizers +############################################################################### +option(SANITIZER "Enable Thread or Address sanitizers" OFF) +string(TOUPPER ${SANITIZER} SANITIZER) + +if (SANITIZER) + if(${SANITIZER} STREQUAL "ADDRESS") + message(STATUS "Enabling address sanitizer...") + # Warning/Error messages + if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug")) + message(WARNING "Address sanitizer results with an optimized (non-Debug) build may be misleading") + endif() + + if("${CMAKE_C_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") + message(STATUS "Building with llvm Address sanitizer Tools") + + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address") + + elseif(CMAKE_COMPILER_IS_GNUCXX) + message(STATUS "Building with Address sanitizer Tools") + + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") + + else() + message(FATAL_ERROR "Address sanitizer requires Clang or GCC. Aborting.") + endif() + + elseif(${SANITIZER} STREQUAL "THREAD") + message(STATUS "Enabling thread sanitizer...") + # Warning/Error messages + if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug")) + message(WARNING "Thread sanitizer results with an optimized (non-Debug) build may be misleading") + endif() + + if("${CMAKE_C_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") + message(STATUS "Building with llvm Thread sanitizer Tools") + + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=thread") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread") + + elseif(CMAKE_COMPILER_IS_GNUCXX) + message(STATUS "Building with Thread sanitizer Tools") + + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=thread -fno-omit-frame-pointer") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread -fno-omit-frame-pointer") + + else() + message(FATAL_ERROR "Thread sanitizer requires Clang or GCC. Aborting.") + endif() + + elseif(${SANITIZER} STREQUAL "UNDEFINED") + message(STATUS "Enabling undefined behavior sanitizer...") + # Warning/Error messages + if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug")) + message(WARNING "Undefined behavior sanitizer results with an optimized (non-Debug) build may be misleading") + endif() + + if(CMAKE_COMPILER_IS_GNUCXX) + message(STATUS "Building with Undefined behavior sanitizer Tools") + + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=undefined -fno-omit-frame-pointer -fsanitize=float-cast-overflow -fsanitize=float-divide-by-zero -fsanitize=bounds-strict") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fno-omit-frame-pointer -fsanitize=float-cast-overflow -fsanitize=float-divide-by-zero -fsanitize=bounds-strict") + + else() + message(FATAL_ERROR "Undefined behavior sanitizer requires GCC. Aborting.") + endif() + + else() + message(WARNING "Sanitizer option not supported. Continuing without any code instrumentation... ") + + endif() +endif() + +############################################################################### +# Set warnings as errors if the thread sanitizer is not being used +############################################################################### +string(FIND ${CMAKE_CXX_FLAGS} "-fsanitize=thread" SANITIZER_THREAD) +if (${SANITIZER_THREAD} EQUAL -1) + message(STATUS "Setting warnings as errors...") + set(CMAKE_COMPILE_WARNING_AS_ERROR ON) +endif() + +############################################################################### +>>>>>>> 25a43a7c3 (Add UBSan workflow and solve its errors (#6386)) # Installation paths ############################################################################### option(APPEND_PROJECT_NAME_TO_INCLUDEDIR diff --git a/include/fastdds/rtps/messages/CDRMessage.hpp b/include/fastdds/rtps/messages/CDRMessage.hpp index 19ed67da4e6..864af35eacd 100644 --- a/include/fastdds/rtps/messages/CDRMessage.hpp +++ b/include/fastdds/rtps/messages/CDRMessage.hpp @@ -402,6 +402,11 @@ inline void CDRMessage::copyToBuffer( const uint32_t length, bool reverse) { + if (length == 0) + { + return; + } + if (reverse) { for (uint32_t i = 0; i < length; i++) diff --git a/include/fastdds/rtps/resources/TimedEvent.h b/include/fastdds/rtps/resources/TimedEvent.h index 1e9efe79726..bae1cc4f25f 100644 --- a/include/fastdds/rtps/resources/TimedEvent.h +++ b/include/fastdds/rtps/resources/TimedEvent.h @@ -106,11 +106,23 @@ class TimedEvent * @param callback Callback called when the event expires. * @param milliseconds Expiration time in milliseconds. */ - TimedEvent( + explicit TimedEvent( ResourceEvent& service, std::function callback, double milliseconds); + /*! + * @brief Construct with an expiration time expressed directly in microseconds. + * + * @param service ResourceEvent object that will operate with the event. + * @param callback Callback called when the event expires. + * @param interval Expiration time in std::chrono::microseconds. + */ + explicit TimedEvent( + ResourceEvent& service, + std::function callback, + std::chrono::microseconds interval); + //! Default destructor. virtual ~TimedEvent(); diff --git a/include/fastdds/rtps/security/logging/LogOptions.h b/include/fastdds/rtps/security/logging/LogOptions.h index 20caf490218..d8428fc4609 100644 --- a/include/fastdds/rtps/security/logging/LogOptions.h +++ b/include/fastdds/rtps/security/logging/LogOptions.h @@ -34,11 +34,11 @@ namespace security { struct LogOptions { //! Whether the log events should be distributed over DDS - bool distribute; + bool distribute = false; //! Level at which log messages will be logged. //! Messages at or below the log_level are logged. - LoggingLevel log_level; + LoggingLevel log_level = LoggingLevel::EMERGENCY_LEVEL; //! Full path to a local file std::string log_file; diff --git a/include/fastdds/statistics/dds/domain/DomainParticipant.hpp b/include/fastdds/statistics/dds/domain/DomainParticipant.hpp index 306e20c3f20..92967e46c50 100644 --- a/include/fastdds/statistics/dds/domain/DomainParticipant.hpp +++ b/include/fastdds/statistics/dds/domain/DomainParticipant.hpp @@ -22,7 +22,16 @@ #include +<<<<<<< HEAD +======= +#include +#include +#include +#include +#include +>>>>>>> 25a43a7c3 (Add UBSan workflow and solve its errors (#6386)) #include +#include #include #include #include @@ -43,6 +52,13 @@ class DomainParticipant : public eprosima::fastdds::dds::DomainParticipant { DomainParticipant() = delete; +protected: + + DomainParticipant( + const eprosima::fastdds::dds::StatusMask& mask); + + friend class eprosima::fastdds::dds::DomainParticipantFactory; + public: /** diff --git a/src/cpp/fastdds/domain/DomainParticipantFactory.cpp b/src/cpp/fastdds/domain/DomainParticipantFactory.cpp index 45c8ca4f4f3..40809125e30 100644 --- a/src/cpp/fastdds/domain/DomainParticipantFactory.cpp +++ b/src/cpp/fastdds/domain/DomainParticipantFactory.cpp @@ -32,9 +32,17 @@ #include #include +<<<<<<< HEAD // We include boost through this internal header, to ensure we use our custom boost config file #include #include +======= +#ifdef FASTDDS_STATISTICS +#include +#endif // ifdef FASTDDS_STATISTICS + +using namespace eprosima::fastdds::xmlparser; +>>>>>>> 25a43a7c3 (Add UBSan workflow and solve its errors (#6386)) using namespace eprosima::fastrtps::xmlparser; @@ -220,12 +228,20 @@ DomainParticipant* DomainParticipantFactory::create_participant( const DomainParticipantQos& pqos = (&qos == &PARTICIPANT_QOS_DEFAULT) ? default_participant_qos_ : qos; - DomainParticipant* dom_part = new DomainParticipant(mask); #ifndef FASTDDS_STATISTICS +<<<<<<< HEAD DomainParticipantImpl* dom_part_impl = new DomainParticipantImpl(dom_part, did, pqos, listen); #else eprosima::fastdds::statistics::dds::DomainParticipantImpl* dom_part_impl = new eprosima::fastdds::statistics::dds::DomainParticipantImpl(dom_part, did, pqos, listen); +======= + DomainParticipant* dom_part = new DomainParticipant(mask); + DomainParticipantImpl* dom_part_impl = new DomainParticipantImpl(dom_part, did, pqos, listener); +#else + statistics::dds::DomainParticipant* dom_part = new statistics::dds::DomainParticipant(mask); + statistics::dds::DomainParticipantImpl* dom_part_impl = + new statistics::dds::DomainParticipantImpl(dom_part, did, pqos, listener); +>>>>>>> 25a43a7c3 (Add UBSan workflow and solve its errors (#6386)) #endif // FASTDDS_STATISTICS { diff --git a/src/cpp/fastdds/publisher/DataWriterImpl.cpp b/src/cpp/fastdds/publisher/DataWriterImpl.cpp index 47afcba7314..acd490dfa07 100644 --- a/src/cpp/fastdds/publisher/DataWriterImpl.cpp +++ b/src/cpp/fastdds/publisher/DataWriterImpl.cpp @@ -1304,6 +1304,72 @@ bool DataWriterImpl::deadline_timer_reschedule() return true; } +<<<<<<< HEAD +======= +void DataWriterImpl::configure_deadline_timer_() +{ + std::unique_lock lock(writer_->getMutex()); + + // Create the timer once + if (deadline_timer_ == nullptr) + { + deadline_timer_ = new TimedEvent( + publisher_->rtps_participant()->get_resource_event(), + [this]() -> bool + { + return deadline_missed(); + }, + // Park timer with a huge interval (prevents spurious callbacks); we'll arm/cancel explicitly + std::chrono::microseconds::max() + ); + } + + // Handle "infinite" and "zero" outside the callback + if (qos_.deadline().period == dds::c_TimeInfinite) + { + deadline_duration_us_ = std::chrono::duration::max(); + deadline_timer_->cancel_timer(); + return; + } + + deadline_duration_us_ = + std::chrono::duration>(qos_.deadline().period.to_ns() * 1e-3); + + if (qos_.deadline().period.to_ns() == 0) + { + deadline_timer_->cancel_timer(); + + deadline_missed_status_.total_count = std::numeric_limits::max(); + deadline_missed_status_.total_count_change = std::numeric_limits::max(); + EPROSIMA_LOG_WARNING( + DATA_WRITER, + "Deadline period is 0, it will be ignored from now on."); + + // Bump once and notify listener exactly once. + notify_deadline_missed_nts_(); + return; + } + + deadline_timer_->update_interval_millisec(qos_.deadline().period.to_ns() * 1e-6); +} + +void DataWriterImpl::notify_deadline_missed_nts_() +{ + StatusMask notify_status = StatusMask::offered_deadline_missed(); + if (auto* listener = get_listener_for(notify_status)) + { + listener->on_offered_deadline_missed(user_datawriter_, deadline_missed_status_); + deadline_missed_status_.total_count_change = 0; + } + +#ifdef FASTDDS_STATISTICS + writer_listener_.notify_status_observer(statistics::StatusKind::DEADLINE_MISSED); +#endif // FASTDDS_STATISTICS + + user_datawriter_->get_statuscondition().get_impl()->set_status(notify_status, true); +} + +>>>>>>> 25a43a7c3 (Add UBSan workflow and solve its errors (#6386)) bool DataWriterImpl::deadline_missed() { assert(qos_.deadline().period != c_TimeInfinite); diff --git a/src/cpp/fastdds/subscriber/DataReaderImpl.cpp b/src/cpp/fastdds/subscriber/DataReaderImpl.cpp index 866b7e6d36e..dbb6bb08eba 100644 --- a/src/cpp/fastdds/subscriber/DataReaderImpl.cpp +++ b/src/cpp/fastdds/subscriber/DataReaderImpl.cpp @@ -20,6 +20,20 @@ #include +<<<<<<< HEAD +======= +#include +#include +#include +#include +#if defined(__has_include) && __has_include() +# include +#endif // if defined(__has_include) && __has_include() + +#include +#include +#include +>>>>>>> 25a43a7c3 (Add UBSan workflow and solve its errors (#6386)) #include #include #include @@ -1050,9 +1064,57 @@ bool DataReaderImpl::deadline_missed() std::unique_lock lock(reader_->getMutex()); +<<<<<<< HEAD deadline_missed_status_.total_count++; deadline_missed_status_.total_count_change++; deadline_missed_status_.last_instance_handle = timer_owner_; +======= + // Create the timer once + if (deadline_timer_ == nullptr) + { + deadline_timer_ = new TimedEvent( + subscriber_->rtps_participant()->get_resource_event(), + [this]() -> bool + { + return deadline_missed(); + }, + // Park timer with a huge interval (prevents spurious callbacks); we'll arm/cancel explicitly + std::chrono::microseconds::max() + ); + } + + // Handle "infinite" and "zero" outside the callback + if (qos_.deadline().period == dds::c_TimeInfinite) + { + deadline_duration_us_ = std::chrono::duration::max(); + deadline_timer_->cancel_timer(); + return; + } + + deadline_duration_us_ = + std::chrono::duration>(qos_.deadline().period.to_ns() * 1e-3); + + if (qos_.deadline().period.to_ns() == 0) + { + deadline_timer_->cancel_timer(); + + deadline_missed_status_.total_count = std::numeric_limits::max(); + deadline_missed_status_.total_count_change = std::numeric_limits::max(); + EPROSIMA_LOG_WARNING( + DATA_READER, + "Deadline period is 0, it will be ignored from now on."); + + // Bump once and notify listener exactly once. + notify_deadline_missed_nts_(); + return; + } + + deadline_timer_->update_interval_millisec(qos_.deadline().period.to_ns() * 1e-6); +} + +void DataReaderImpl::notify_deadline_missed_nts_() +{ +>>>>>>> 25a43a7c3 (Add UBSan workflow and solve its errors (#6386)) StatusMask notify_status = StatusMask::requested_deadline_missed(); auto listener = get_listener_for(notify_status); if (nullptr != listener) @@ -1287,8 +1349,29 @@ LivelinessChangedStatus& DataReaderImpl::update_liveliness_status( SampleLostStatus& DataReaderImpl::update_sample_lost_status( int32_t sample_lost_since_last_update) { - sample_lost_status_.total_count += sample_lost_since_last_update; - sample_lost_status_.total_count_change += sample_lost_since_last_update; + constexpr int32_t int32_max = std::numeric_limits::max(); + + // Perform the addition in 64-bit space to avoid signed-integer-overflow UB on int32_t + const int32_t prev_total = sample_lost_status_.total_count; + const int64_t new_total = + static_cast(prev_total) + sample_lost_since_last_update; + const int64_t new_change = + static_cast(sample_lost_status_.total_count_change) + sample_lost_since_last_update; + + // Saturate at int32_t max + sample_lost_status_.total_count = + (new_total > int32_max) ? int32_max : static_cast(new_total); + sample_lost_status_.total_count_change = + (new_change > int32_max) ? int32_max : static_cast(new_change); + + // Warn only when the counter reaches the max value + if (prev_total < int32_max && sample_lost_status_.total_count == int32_max) + { + EPROSIMA_LOG_WARNING(DATA_READER, + "SampleLostStatus counter for DataReader " + << guid() << " reached max value. The cumulative count will remain saturated, " + << "but listener notifications for further lost samples will continue."); + } return sample_lost_status_; } diff --git a/src/cpp/rtps/common/SerializedPayload.cpp b/src/cpp/rtps/common/SerializedPayload.cpp new file mode 100644 index 00000000000..98ea4738373 --- /dev/null +++ b/src/cpp/rtps/common/SerializedPayload.cpp @@ -0,0 +1,154 @@ +// Copyright 2024 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/*! + * @file SerializedPayload.cpp + */ + +#include + +namespace eprosima { +namespace fastdds { +namespace rtps { + +SerializedPayload_t& SerializedPayload_t::operator = ( + SerializedPayload_t&& other) noexcept +{ + if (this == &other) + { + return *this; + } + + encapsulation = other.encapsulation; + length = other.length; + data = other.data; + max_size = other.max_size; + pos = other.pos; + payload_owner = other.payload_owner; + is_serialized_key = other.is_serialized_key; + + other.encapsulation = CDR_BE; + other.length = 0; + other.data = nullptr; + other.max_size = 0; + other.pos = 0; + other.payload_owner = nullptr; + other.is_serialized_key = false; + + return *this; +} + +SerializedPayload_t::~SerializedPayload_t() +{ + if (payload_owner != nullptr) + { + payload_owner->release_payload(*this); + } + this->empty(); +} + +bool SerializedPayload_t::operator == ( + const SerializedPayload_t& other) const +{ + return ((encapsulation == other.encapsulation) && + (is_serialized_key == other.is_serialized_key) && + (length == other.length) && + (length == 0 || 0 == memcmp(data, other.data, length))); +} + +bool SerializedPayload_t::copy( + const SerializedPayload_t* serData, + bool with_limit) +{ + length = serData->length; + + if (serData->length > max_size) + { + if (with_limit) + { + return false; + } + else + { + this->reserve(serData->length); + } + } + encapsulation = serData->encapsulation; + is_serialized_key = serData->is_serialized_key; + if (length == 0) + { + return true; + } + memcpy(data, serData->data, length); + return true; +} + +bool SerializedPayload_t::reserve_fragmented( + SerializedPayload_t* serData) +{ + length = serData->length; + max_size = serData->length; + encapsulation = serData->encapsulation; + is_serialized_key = serData->is_serialized_key; + data = (octet*)calloc(length, sizeof(octet)); + return true; +} + +void SerializedPayload_t::empty() +{ + assert(payload_owner == nullptr); + + length = 0; + encapsulation = CDR_BE; + max_size = 0; + if (data != nullptr) + { + free(data); + } + data = nullptr; + is_serialized_key = false; +} + +void SerializedPayload_t::reserve( + uint32_t new_size) +{ + if (new_size <= this->max_size) + { + return; + } + if (data == nullptr) + { + data = (octet*)calloc(new_size, sizeof(octet)); + if (!data) + { + throw std::bad_alloc(); + } + } + else + { + void* old_data = data; + data = (octet*)realloc(data, new_size); + if (!data) + { + free(old_data); + throw std::bad_alloc(); + } + memset(data + max_size, 0, (new_size - max_size) * sizeof(octet)); + } + max_size = new_size; +} + +} /* namespace rtps */ +} /* namespace fastdds */ +} /* namespace eprosima */ diff --git a/src/cpp/rtps/history/PoolConfig.h b/src/cpp/rtps/history/PoolConfig.h index 878ce087b9c..31f5447f3f7 100644 --- a/src/cpp/rtps/history/PoolConfig.h +++ b/src/cpp/rtps/history/PoolConfig.h @@ -19,8 +19,16 @@ #ifndef RTPS_HISTORY_POOLCONFIG_H_ #define RTPS_HISTORY_POOLCONFIG_H_ +<<<<<<< HEAD #include #include +======= +#include +#include + +#include +#include +>>>>>>> 25a43a7c3 (Add UBSan workflow and solve its errors (#6386)) namespace eprosima { namespace fastrtps { @@ -56,6 +64,24 @@ struct PoolConfig : public BasicPoolConfig //! Maximum number of elements in the pool. Default value is 0, indicating to make allocations until they fail. uint32_t maximum_size; + /** + * Compute reserved + extra in int64_t and clamp to UINT32_MAX, avoiding the signed overflow + * A reserved value <= 0 means "no preallocation" (initial) or "infinite maximum" (max) + * + * @param reserved initial or maximum reserved caches + * @param extra extra caches to be added to the reserved caches + * @return the sum of reserved and extra, clamped to UINT32_MAX, or 0 if reserved is <= 0 + */ + static constexpr uint32_t clamped_reserved_plus_extra( + int32_t reserved, + int32_t extra) noexcept + { + return reserved <= 0 ? 0u : + (static_cast(reserved) + extra >= + static_cast((std::numeric_limits::max)()) ? (std::numeric_limits::max)() : + static_cast(static_cast(reserved) + extra)); + } + /** * Transform a HistoryAttributes object into a PoolConfig * @@ -70,16 +96,10 @@ struct PoolConfig : public BasicPoolConfig { history_attr.memoryPolicy, history_attr.payloadMaxSize, - // Negative or 0 means no preallocation. - // Otherwise, we need to reserve the extra to avoid dynamic allocations after the pools are created. - static_cast( - history_attr.initialReservedCaches <= 0 ? - 0 : history_attr.initialReservedCaches + history_attr.extraReservedCaches), - // Negative or 0 means infinite maximum. - // Otherwise, we need to allow the extra. - static_cast( - history_attr.maximumReservedCaches <= 0 ? - 0 : history_attr.maximumReservedCaches + history_attr.extraReservedCaches) + clamped_reserved_plus_extra( + history_attr.initialReservedCaches, history_attr.extraReservedCaches), + clamped_reserved_plus_extra( + history_attr.maximumReservedCaches, history_attr.extraReservedCaches) }; } diff --git a/src/cpp/rtps/resources/TimedEvent.cpp b/src/cpp/rtps/resources/TimedEvent.cpp index c109a88e3b1..8725550cbe1 100644 --- a/src/cpp/rtps/resources/TimedEvent.cpp +++ b/src/cpp/rtps/resources/TimedEvent.cpp @@ -39,6 +39,17 @@ TimedEvent::TimedEvent( service_.register_timer(impl_); } +TimedEvent::TimedEvent( + ResourceEvent& service, + std::function callback, + std::chrono::microseconds interval) + : service_(service) + , impl_(nullptr) +{ + impl_ = new TimedEventImpl(callback, interval); + service_.register_timer(impl_); +} + TimedEvent::~TimedEvent() { service_.unregister_timer(impl_); diff --git a/src/cpp/rtps/security/SecurityManager.cpp b/src/cpp/rtps/security/SecurityManager.cpp index ded03f8bf2c..f5acbf80f55 100644 --- a/src/cpp/rtps/security/SecurityManager.cpp +++ b/src/cpp/rtps/security/SecurityManager.cpp @@ -319,10 +319,15 @@ bool SecurityManager::init( { crypto_plugin_->set_logger(logging_plugin_, exception); + // When no access control plugin is configured, local_permissions_handle_ is null + NilHandle nil_permissions_handle; + const PermissionsHandle& permissions_handle = (local_permissions_handle_ != nullptr) ? + *local_permissions_handle_ : nil_permissions_handle; + local_participant_crypto_handle_ = crypto_plugin_->cryptokeyfactory()->register_local_participant( *local_identity_handle_, - *local_permissions_handle_, + permissions_handle, participant_properties.properties(), attributes, exception); diff --git a/src/cpp/statistics/fastdds/domain/DomainParticipant.cpp b/src/cpp/statistics/fastdds/domain/DomainParticipant.cpp index 7eb3648b438..cb8f4a50c31 100644 --- a/src/cpp/statistics/fastdds/domain/DomainParticipant.cpp +++ b/src/cpp/statistics/fastdds/domain/DomainParticipant.cpp @@ -31,7 +31,17 @@ namespace fastdds { namespace statistics { namespace dds { +<<<<<<< HEAD ReturnCode_t DomainParticipant::enable_statistics_datawriter( +======= +DomainParticipant::DomainParticipant( + const eprosima::fastdds::dds::StatusMask& mask) + : eprosima::fastdds::dds::DomainParticipant(mask) +{ +} + +fastdds::dds::ReturnCode_t DomainParticipant::enable_statistics_datawriter( +>>>>>>> 25a43a7c3 (Add UBSan workflow and solve its errors (#6386)) const std::string& topic_name, const eprosima::fastdds::dds::DataWriterQos& dwqos) { diff --git a/test/blackbox/common/BlackboxTestsDeadlineQos.cpp b/test/blackbox/common/BlackboxTestsDeadlineQos.cpp index b97bd366859..29729486416 100644 --- a/test/blackbox/common/BlackboxTestsDeadlineQos.cpp +++ b/test/blackbox/common/BlackboxTestsDeadlineQos.cpp @@ -28,12 +28,14 @@ using namespace eprosima::fastrtps; using namespace eprosima::fastrtps::rtps; +namespace { enum communication_type { TRANSPORT, INTRAPROCESS, DATASHARING }; +} // namespace class DeadlineQos : public testing::TestWithParam { diff --git a/test/blackbox/common/BlackboxTestsDiscovery.cpp b/test/blackbox/common/BlackboxTestsDiscovery.cpp index 646bb8d1ba9..b6fec3db8fa 100644 --- a/test/blackbox/common/BlackboxTestsDiscovery.cpp +++ b/test/blackbox/common/BlackboxTestsDiscovery.cpp @@ -35,12 +35,14 @@ using namespace eprosima::fastrtps::rtps; using test_UDPv4Transport = eprosima::fastdds::rtps::test_UDPv4Transport; using test_UDPv4TransportDescriptor = eprosima::fastdds::rtps::test_UDPv4TransportDescriptor; +namespace { enum communication_type { TRANSPORT, INTRAPROCESS, DATASHARING }; +} // namespace class Discovery : public testing::TestWithParam { diff --git a/test/blackbox/common/BlackboxTestsLifespanQoS.cpp b/test/blackbox/common/BlackboxTestsLifespanQoS.cpp index d27f72a2656..24a7cab9d7b 100644 --- a/test/blackbox/common/BlackboxTestsLifespanQoS.cpp +++ b/test/blackbox/common/BlackboxTestsLifespanQoS.cpp @@ -27,12 +27,14 @@ using namespace eprosima::fastrtps; using namespace eprosima::fastrtps::rtps; +namespace { enum communication_type { TRANSPORT, INTRAPROCESS, DATASHARING }; +} // namespace class LifespanQos : public testing::TestWithParam { diff --git a/test/blackbox/common/BlackboxTestsLivelinessQos.cpp b/test/blackbox/common/BlackboxTestsLivelinessQos.cpp index 4d17d8b5f7a..771052906e4 100644 --- a/test/blackbox/common/BlackboxTestsLivelinessQos.cpp +++ b/test/blackbox/common/BlackboxTestsLivelinessQos.cpp @@ -32,12 +32,14 @@ using namespace eprosima::fastrtps; using namespace eprosima::fastrtps::rtps; +namespace { enum communication_type { TRANSPORT, INTRAPROCESS, DATASHARING }; +} // namespace class LivelinessQos : public testing::TestWithParam { diff --git a/test/blackbox/common/BlackboxTestsNetworkConf.cpp b/test/blackbox/common/BlackboxTestsNetworkConf.cpp index 48cefc22454..8f4a09efdf8 100644 --- a/test/blackbox/common/BlackboxTestsNetworkConf.cpp +++ b/test/blackbox/common/BlackboxTestsNetworkConf.cpp @@ -27,10 +27,12 @@ using namespace eprosima::fastrtps; using namespace eprosima::fastrtps::rtps; +namespace { enum communication_type { TRANSPORT }; +} // namespace class NetworkConfig : public testing::TestWithParam> { diff --git a/test/blackbox/common/BlackboxTestsPubSubBasic.cpp b/test/blackbox/common/BlackboxTestsPubSubBasic.cpp index 9f80d08bf9f..0eb4305d5fe 100644 --- a/test/blackbox/common/BlackboxTestsPubSubBasic.cpp +++ b/test/blackbox/common/BlackboxTestsPubSubBasic.cpp @@ -34,12 +34,14 @@ using namespace eprosima::fastrtps; using namespace eprosima::fastrtps::rtps; +namespace { enum communication_type { TRANSPORT, INTRAPROCESS, DATASHARING }; +} // namespace class PubSubBasic : public testing::TestWithParam> { diff --git a/test/blackbox/common/BlackboxTestsPubSubFragments.cpp b/test/blackbox/common/BlackboxTestsPubSubFragments.cpp index 1ab90a11750..3585dbf89c6 100644 --- a/test/blackbox/common/BlackboxTestsPubSubFragments.cpp +++ b/test/blackbox/common/BlackboxTestsPubSubFragments.cpp @@ -27,12 +27,14 @@ using namespace eprosima::fastrtps::rtps; using test_UDPv4Transport = eprosima::fastdds::rtps::test_UDPv4Transport; using test_UDPv4TransportDescriptor = eprosima::fastdds::rtps::test_UDPv4TransportDescriptor; +namespace { enum communication_type { TRANSPORT, INTRAPROCESS, DATASHARING }; +} // namespace using test_params = std::tuple; diff --git a/test/blackbox/common/BlackboxTestsPubSubHistory.cpp b/test/blackbox/common/BlackboxTestsPubSubHistory.cpp index 9845079a4ad..e16c37536e3 100644 --- a/test/blackbox/common/BlackboxTestsPubSubHistory.cpp +++ b/test/blackbox/common/BlackboxTestsPubSubHistory.cpp @@ -28,12 +28,14 @@ using namespace eprosima::fastrtps; using namespace eprosima::fastdds::rtps; +namespace { enum communication_type { TRANSPORT, INTRAPROCESS, DATASHARING }; +} // namespace using test_params = std::tuple; diff --git a/test/blackbox/common/BlackboxTestsRealtimeAllocations.cpp b/test/blackbox/common/BlackboxTestsRealtimeAllocations.cpp index 1ccebb749a6..57358d7ea79 100644 --- a/test/blackbox/common/BlackboxTestsRealtimeAllocations.cpp +++ b/test/blackbox/common/BlackboxTestsRealtimeAllocations.cpp @@ -23,12 +23,14 @@ using namespace eprosima::fastrtps; using namespace eprosima::fastrtps::rtps; +namespace { enum communication_type { TRANSPORT, INTRAPROCESS, DATASHARING }; +} // namespace class RealtimeAllocations : public testing::TestWithParam { diff --git a/test/blackbox/common/BlackboxTestsSecurity.cpp b/test/blackbox/common/BlackboxTestsSecurity.cpp index 7c9c6a0594a..f788295dfa9 100644 --- a/test/blackbox/common/BlackboxTestsSecurity.cpp +++ b/test/blackbox/common/BlackboxTestsSecurity.cpp @@ -42,6 +42,7 @@ using namespace eprosima::fastrtps::rtps; using test_UDPv4Transport = eprosima::fastdds::rtps::test_UDPv4Transport; using test_UDPv4TransportDescriptor = eprosima::fastdds::rtps::test_UDPv4TransportDescriptor; +namespace { enum communication_type { TRANSPORT, @@ -49,7 +50,204 @@ enum communication_type DATASHARING }; +<<<<<<< HEAD class Security : public testing::TestWithParam +======= +enum reliability +{ + TEST_BEST_EFFORT, + TEST_RELIABLE +}; +} // namespace + +// A LogConsumer that just counts the number of entries consumed +struct TestConsumer : public eprosima::fastdds::dds::LogConsumer +{ + TestConsumer( + std::atomic_size_t& n_logs_ref) + : n_logs_(n_logs_ref) + { + } + + void Consume( + const eprosima::fastdds::dds::Log::Entry&) override + { + ++n_logs_; + } + +private: + + std::atomic_size_t& n_logs_; +}; + +static void fill_pub_auth( + PropertyPolicy& policy) +{ + policy.properties().emplace_back("dds.sec.auth.plugin", "builtin.PKI-DH"); + policy.properties().emplace_back("dds.sec.auth.builtin.PKI-DH.identity_ca", + "file://" + std::string(certs_path) + "/maincacert.pem"); + policy.properties().emplace_back("dds.sec.auth.builtin.PKI-DH.identity_certificate", + "file://" + std::string(certs_path) + "/mainpubcert.pem"); + policy.properties().emplace_back("dds.sec.auth.builtin.PKI-DH.private_key", + "file://" + std::string(certs_path) + "/mainpubkey.pem"); + + // Select the key agreement algorithm based on process id + switch (static_cast(GET_PID()) % 4u) + { + // Automatic selection + case 1u: + policy.properties().emplace_back("dds.sec.auth.builtin.PKI-DH.preferred_key_agreement", "AUTO"); + break; + // Force DH + case 2u: + policy.properties().emplace_back("dds.sec.auth.builtin.PKI-DH.preferred_key_agreement", "DH"); + break; + // Force ECDH + case 3u: + policy.properties().emplace_back("dds.sec.auth.builtin.PKI-DH.preferred_key_agreement", "ECDH"); + break; + // Leave default + case 0u: + default: + break; + } +} + +static void fill_sub_auth( + PropertyPolicy& policy) +{ + policy.properties().emplace_back("dds.sec.auth.plugin", "builtin.PKI-DH"); + policy.properties().emplace_back("dds.sec.auth.builtin.PKI-DH.identity_ca", + "file://" + std::string(certs_path) + "/maincacert.pem"); + policy.properties().emplace_back("dds.sec.auth.builtin.PKI-DH.identity_certificate", + "file://" + std::string(certs_path) + "/mainsubcert.pem"); + policy.properties().emplace_back("dds.sec.auth.builtin.PKI-DH.private_key", + "file://" + std::string(certs_path) + "/mainsubkey.pem"); + + // Select the key agreement algorithm based on process id + switch (static_cast(GET_PID()) % 4u) + { + // Automatic selection + case 1u: + policy.properties().emplace_back("dds.sec.auth.builtin.PKI-DH.preferred_key_agreement", "AUTO"); + break; + // Force DH + case 2u: + policy.properties().emplace_back("dds.sec.auth.builtin.PKI-DH.preferred_key_agreement", "DH"); + break; + // Force ECDH + case 3u: + policy.properties().emplace_back("dds.sec.auth.builtin.PKI-DH.preferred_key_agreement", "ECDH"); + break; + // Leave default + case 0u: + default: + break; + } +} + +static void fill_access( + PropertyPolicy& policy, + const std::string& governance_file = "governance_only_auth.smime", + const std::string& permissions_file = "permissions.smime", + const std::string& permissions_ca_file = "maincacert.pem") +{ + policy.properties().emplace_back("dds.sec.access.plugin", "builtin.Access-Permissions"); + policy.properties().emplace_back("dds.sec.access.builtin.Access-Permissions.permissions_ca", + "file://" + std::string(certs_path) + "/" + permissions_ca_file); + policy.properties().emplace_back("dds.sec.access.builtin.Access-Permissions.governance", + "file://" + std::string(certs_path) + "/" + governance_file); + policy.properties().emplace_back("dds.sec.access.builtin.Access-Permissions.permissions", + "file://" + std::string(certs_path) + "/" + permissions_file); +} + +static void fill_crypto( + PropertyPolicy& policy) +{ + policy.properties().emplace_back("dds.sec.crypto.plugin", "builtin.AES-GCM-GMAC"); +} + +static void CommonPermissionsConfigureWriter( + PubSubWriter& writer, + const std::string& governance_file, + const std::string& permissions_file, + const PropertyPolicy& extra_properties) +{ + PropertyPolicy pub_property_policy(extra_properties); + fill_pub_auth(pub_property_policy); + fill_access(pub_property_policy, governance_file, permissions_file); + fill_crypto(pub_property_policy); + + writer.property_policy(pub_property_policy); +} + +static void CommonPermissionsConfigureReader( + PubSubReader& reader, + const std::string& governance_file, + const std::string& permissions_file, + const PropertyPolicy& extra_properties) +{ + PropertyPolicy sub_property_policy(extra_properties); + fill_sub_auth(sub_property_policy); + fill_access(sub_property_policy, governance_file, permissions_file); + fill_crypto(sub_property_policy); + + reader.property_policy(sub_property_policy); +} + +static void CommonPermissionsConfigure( + PubSubReader& reader, + PubSubWriter& writer, + const std::string& governance_file, + const std::string& permissions_file, + const PropertyPolicy& extra_properties = PropertyPolicy()) +{ + CommonPermissionsConfigureReader(reader, governance_file, permissions_file, extra_properties); + CommonPermissionsConfigureWriter(writer, governance_file, permissions_file, extra_properties); +} + +static void CommonPermissionsConfigureWriter( + PubSubWriter& writer, + const std::string& governance_file, + const std::string& permissions_file, + const PropertyPolicy& extra_properties) +{ + PropertyPolicy pub_property_policy(extra_properties); + + fill_pub_auth(pub_property_policy); + fill_access(pub_property_policy, governance_file, permissions_file); + fill_crypto(pub_property_policy); + + writer.property_policy(pub_property_policy); +} + +static void CommonPermissionsConfigureReader( + PubSubReader& reader, + const std::string& governance_file, + const std::string& permissions_file, + const PropertyPolicy& extra_properties) +{ + PropertyPolicy sub_property_policy(extra_properties); + fill_sub_auth(sub_property_policy); + fill_access(sub_property_policy, governance_file, permissions_file); + fill_crypto(sub_property_policy); + + reader.property_policy(sub_property_policy); +} + +static void CommonPermissionsConfigure( + PubSubReader& reader, + PubSubWriter& writer, + const std::string& governance_file, + const std::string& permissions_file, + const PropertyPolicy& extra_properties = PropertyPolicy()) +{ + CommonPermissionsConfigureReader(reader, governance_file, permissions_file, extra_properties); + CommonPermissionsConfigureWriter(writer, governance_file, permissions_file, extra_properties); +} + +class Security : public testing::TestWithParam> +>>>>>>> 25a43a7c3 (Add UBSan workflow and solve its errors (#6386)) { public: diff --git a/test/blackbox/common/BlackboxTestsTransportTCP.cpp b/test/blackbox/common/BlackboxTestsTransportTCP.cpp index e162fec7ef8..efaaa062f4e 100644 --- a/test/blackbox/common/BlackboxTestsTransportTCP.cpp +++ b/test/blackbox/common/BlackboxTestsTransportTCP.cpp @@ -33,10 +33,12 @@ using namespace eprosima::fastrtps; using namespace eprosima::fastrtps::rtps; +namespace { enum communication_type { TRANSPORT }; +} // namespace class TransportTCP : public testing::TestWithParam> { diff --git a/test/blackbox/common/BlackboxTestsTransportUDP.cpp b/test/blackbox/common/BlackboxTestsTransportUDP.cpp index fb142e49661..22ee8bd0b0d 100644 --- a/test/blackbox/common/BlackboxTestsTransportUDP.cpp +++ b/test/blackbox/common/BlackboxTestsTransportUDP.cpp @@ -35,10 +35,12 @@ using namespace eprosima::fastrtps; using namespace eprosima::fastrtps::rtps; +namespace { enum communication_type { TRANSPORT }; +} // namespace class TransportUDP : public testing::TestWithParam> { @@ -1175,6 +1177,100 @@ TEST(TransportUDP, MaliciousHeartbeatBigStart) EXPECT_LT(end - start, std::chrono::seconds(15)); } +// Regression test issue #24365: a sequence-number gap larger than INT32_MAX between two DATA submessages +// must not overflow DataReaderImpl::SampleLostStatus::total_count. The expected behavior is saturation at INT32_MAX. +TEST(TransportUDP, MaliciousDataLargeSequenceNumberGap) +{ + auto udp_transport = std::make_shared(); + + PubSubWriter writer(TEST_TOPIC_NAME); + PubSubReader reader(TEST_TOPIC_NAME); + + struct MaliciousData + { + std::array rtps_id{ {'R', 'T', 'P', 'S'} }; + std::array protocol_version{ {2, 3} }; + std::array vendor_id{ {0x01, 0x0F} }; + GuidPrefix_t sender_prefix{}; + + struct DataSubMsg + { + uint8_t submessage_id = 0x15; // DATA +#if FASTDDS_IS_BIG_ENDIAN_TARGET + uint8_t flags = 0x04; // D=1 +#else + uint8_t flags = 0x05; // E=1, D=1 +#endif // FASTDDS_IS_BIG_ENDIAN_TARGET + uint16_t octets_to_next_header = 24; + uint16_t extra_flags = 0; + uint16_t octets_to_inline_qos = 16; + EntityId_t reader_id{}; + EntityId_t writer_id{}; + SequenceNumber_t sn{ 0, 1u }; + // Minimal serialized payload: just the CDR_LE encapsulation header. + std::array serialized_payload{ {0x00, 0x01, 0x00, 0x00} }; + } + data; + }; + + UDPMessageSender fake_msg_sender; + + reader.disable_builtin_transport().add_user_transport_to_pparams(udp_transport) + .history_depth(10) + .reliability(eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS); + writer.history_depth(10).reliability(eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS); + + Locator_t reader_locator; + ASSERT_TRUE(IPLocator::setIPv4(reader_locator, "127.0.0.1")); + reader_locator.port = global_port; + reader.add_to_unicast_locator_list("127.0.0.1", global_port); + + reader.init(); + ASSERT_TRUE(reader.isInitialized()); + writer.init(); + ASSERT_TRUE(writer.isInitialized()); + + reader.wait_discovery(); + writer.wait_discovery(); + + auto send_data_with_sn = [&](const SequenceNumber_t& sn) + { + auto writer_guid = writer.datawriter_guid(); + + MaliciousData malicious_packet{}; + malicious_packet.sender_prefix = writer_guid.guidPrefix; + malicious_packet.data.writer_id = writer_guid.entityId; + malicious_packet.data.reader_id = reader.datareader_guid().entityId; + malicious_packet.data.sn = sn; + + CDRMessage_t msg(0); + uint32_t msg_len = static_cast(sizeof(malicious_packet)); + msg.init(reinterpret_cast(&malicious_packet), msg_len); + msg.length = msg_len; + msg.pos = msg_len; + fake_msg_sender.send(msg, reader_locator); + }; + + // Packet 1: establish baseline (SN = 1). + send_data_with_sn(SequenceNumber_t{ 0, 1u }); + // Packet 2: small gap (SN = 3) -> total_count becomes 1. + send_data_with_sn(SequenceNumber_t{ 0, 3u }); + // Packet 3: SN = 0x80000004 -> per-event lost saturates at INT32_MAX. + // Without saturation in update_sample_lost_status, total_count = 1 + INT32_MAX overflows. + send_data_with_sn(SequenceNumber_t{ 0, 0x80000004u }); + + // Allow the reader thread to drain the three injected packets. + std::this_thread::sleep_for(std::chrono::seconds(1)); + + // If the previous step did not abort the process under UBSan, the counter must remain + // non-negative (no wrap) and within int32_t range (saturated, not overflowed). + // auto status = reader.get_sample_lost_status(); + // EXPECT_GE(status.total_count, 0); + // EXPECT_LE(status.total_count, std::numeric_limits::max()); + + reader.destroy(); +} + // Test for ==operator UDPTransportDescriptor is not required as it is an abstract class and in UDPv4 is same method // Test for copy UDPTransportDescriptor is not required as it is an abstract class and in UDPv4 is same method @@ -1279,4 +1375,3 @@ GTEST_INSTANTIATE_TEST_MACRO(TransportUDP, } }); - diff --git a/test/blackbox/common/BlackboxTestsVolatile.cpp b/test/blackbox/common/BlackboxTestsVolatile.cpp index 6351d0baea1..f7e82fd9ae1 100644 --- a/test/blackbox/common/BlackboxTestsVolatile.cpp +++ b/test/blackbox/common/BlackboxTestsVolatile.cpp @@ -28,12 +28,14 @@ using namespace eprosima::fastrtps; using test_UDPv4Transport = eprosima::fastdds::rtps::test_UDPv4Transport; using test_UDPv4TransportDescriptor = eprosima::fastdds::rtps::test_UDPv4TransportDescriptor; +namespace { enum communication_type { TRANSPORT, INTRAPROCESS, DATASHARING }; +} // namespace class Volatile : public testing::TestWithParam { diff --git a/test/blackbox/common/DDSBlackboxTestsDataReader.cpp b/test/blackbox/common/DDSBlackboxTestsDataReader.cpp index 037844b3178..09015ea4b57 100644 --- a/test/blackbox/common/DDSBlackboxTestsDataReader.cpp +++ b/test/blackbox/common/DDSBlackboxTestsDataReader.cpp @@ -32,12 +32,14 @@ using namespace eprosima::fastrtps::rtps; std::string("incompatible_") + TEST_TOPIC_NAME) +namespace { enum communication_type { TRANSPORT, INTRAPROCESS, DATASHARING }; +} // namespace class DDSDataReader : public testing::TestWithParam { @@ -414,6 +416,1001 @@ TEST(DDSDataReader, default_qos_large_history_depth) ASSERT_TRUE(reader.isInitialized()); } +<<<<<<< HEAD +======= +/** + * Utility class to set some values other than default to those Qos common to Topic and DataReader. + * + * This is a class instead of a free function to avoid linking with its TestsDataWriter counterpart. + */ +class TestsDataReaderQosCommonUtils +{ +public: + + // Set common Qos values to both TopicQos and DataReaderQos + template + static void set_common_qos( + T& qos) + { + qos.durability_service().history_kind = eprosima::fastdds::dds::KEEP_ALL_HISTORY_QOS; + qos.reliability().kind = eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS; + qos.durability().kind = eprosima::fastdds::dds::TRANSIENT_LOCAL_DURABILITY_QOS; + qos.deadline().period = {0, 500000000}; + qos.latency_budget().duration = 0; + qos.liveliness().kind = eprosima::fastdds::dds::MANUAL_BY_PARTICIPANT_LIVELINESS_QOS; + qos.resource_limits().max_samples = 1000; + qos.ownership().kind = eprosima::fastdds::dds::EXCLUSIVE_OWNERSHIP_QOS; + // Representation is not on the same place in DataReaderQos and TopicQos + set_representation_qos(qos); + qos.history().kind = eprosima::fastdds::dds::KEEP_ALL_HISTORY_QOS; + } + +private: + + // Set representation Qos (as it is not in the same place in DataReaderQos and TopicQos) + template + static void set_representation_qos( + T& qos); +}; + +// Specialization for DataReaderQos +template<> +void TestsDataReaderQosCommonUtils::set_representation_qos( + eprosima::fastdds::dds::DataReaderQos& qos) +{ + qos.representation().m_value.push_back( + eprosima::fastdds::dds::DataRepresentationId_t::XCDR2_DATA_REPRESENTATION); +} + +// Specialization for TopicQos +template<> +void TestsDataReaderQosCommonUtils::set_representation_qos( + eprosima::fastdds::dds::TopicQos& qos) +{ + qos.representation().m_value.push_back(eprosima::fastdds::dds::DataRepresentationId_t::XCDR2_DATA_REPRESENTATION); +} + +/* + * This test: + * 1. Creates a Topic with custom Qos + * 2. Updates the default DataReader Qos that are not in common with Topic Qos with non-default values + * 3. Creates a DataReader with DATAREADER_QOS_USE_TOPIC_QOS + * 4. Checks that the used Qos are the merge between the default ones and the Topic ones + */ +TEST(DDSDataReader, datareader_qos_use_topic_qos) +{ + using namespace eprosima::fastdds::dds; + + /* Create a topic with custom Qos */ + // Set Topic Qos different from default + TopicQos topic_qos; + TestsDataReaderQosCommonUtils::set_common_qos(topic_qos); + + // Create DomainParticipant + DomainParticipant* participant = + DomainParticipantFactory::get_instance()->create_participant(0, PARTICIPANT_QOS_DEFAULT); + ASSERT_NE(participant, nullptr); + + // Create Topic + TypeSupport type_support; + type_support.reset(new HelloWorldPubSubType()); + type_support.register_type(participant, "HelloWorld"); + Topic* topic = participant->create_topic("HelloWorldTopic", "HelloWorld", topic_qos); + + /* Create a DataReader with modified default Qos using the Topic Qos */ + // Create the Subscriber + Subscriber* subscriber = participant->create_subscriber(SUBSCRIBER_QOS_DEFAULT); + ASSERT_NE(subscriber, nullptr); + + // Change default DataReader Qos (only those that are different from Topic Qos) + DataReaderQos control_qos; + control_qos.reader_data_lifecycle().autopurge_no_writer_samples_delay = {3, 0}; + control_qos.user_data().push_back(0); + control_qos.endpoint().entity_id = 1; + control_qos.reader_resource_limits().matched_publisher_allocation = + ResourceLimitedContainerConfig::fixed_size_configuration(1u); + control_qos.data_sharing().off(); + subscriber->set_default_datareader_qos(control_qos); + + // Create DataReader with DATAREADER_QOS_USE_TOPIC_QOS + DataReader* reader = subscriber->create_datareader(topic, DATAREADER_QOS_USE_TOPIC_QOS); + ASSERT_NE(reader, nullptr); + + /* Check that used Qos are the merge between the default ones and the Topic ones */ + // Set the topic values on the control DataReaderQos + TestsDataReaderQosCommonUtils::set_common_qos(control_qos); + + // Get used DataReader Qos + DataReaderQos test_qos = reader->get_qos(); + + // Check that the Qos that are not in common with Topic Qos are correctly set as the default ones, + // and that the rest of the Qos are left unmodified + ASSERT_EQ(control_qos, test_qos); +} + +bool validate_publication_builtin_topic_data( + const eprosima::fastdds::rtps::PublicationBuiltinTopicData& pubdata, + const eprosima::fastdds::dds::DataWriter& datawriter) +{ + bool ret = true; + + auto dw_qos = datawriter.get_qos(); + auto pub_qos = datawriter.get_publisher()->get_qos(); + + eprosima::fastdds::rtps::BuiltinTopicKey_t dw_key, part_key; + + entity_id_to_builtin_topic_key(dw_key, datawriter.guid().entityId); + guid_prefix_to_builtin_topic_key(part_key, datawriter.get_publisher()->get_participant()->guid().guidPrefix); + + ret &= (0 == memcmp(pubdata.key.value, dw_key.value, sizeof(eprosima::fastdds::rtps::BuiltinTopicKey_t))); + ret &= + (0 == + memcmp(pubdata.participant_key.value, part_key.value, + sizeof(eprosima::fastdds::rtps::BuiltinTopicKey_t))); + + ret &= (pubdata.topic_name.to_string() == datawriter.get_topic()->get_name()); + ret &= (pubdata.type_name.to_string() == datawriter.get_topic()->get_type_name()); + + // DataWriter Qos + ret &= (pubdata.durability == dw_qos.durability()); + ret &= (pubdata.durability_service == dw_qos.durability_service()); + ret &= (pubdata.deadline == dw_qos.deadline()); + ret &= (pubdata.latency_budget == dw_qos.latency_budget()); + ret &= (pubdata.liveliness == dw_qos.liveliness()); + ret &= (pubdata.reliability == dw_qos.reliability()); + ret &= (pubdata.lifespan == dw_qos.lifespan()); + ret &= ( + (pubdata.user_data.size() == dw_qos.user_data().size()) && + (pubdata.user_data.size() == 0 || + 0 == memcmp(pubdata.user_data.data(), dw_qos.user_data().data(), pubdata.user_data.size()))); + ret &= (pubdata.ownership == dw_qos.ownership()); + ret &= (pubdata.ownership_strength == dw_qos.ownership_strength()); + ret &= (pubdata.destination_order == dw_qos.destination_order()); + + // Publisher Qos + ret &= (pubdata.presentation == pub_qos.presentation()); + ret &= (pubdata.partition.getNames() == pub_qos.partition().getNames()); + // topic_data not implemented + // group_data too + + return ret; +} + +/** + * @test DDS-DR-API-GMPD-01 + * + * get_matched_publication_data() must return RETCODE_BAD_PARAMETER + * if the publication is not matched. + */ +TEST(DDSDataReader, datareader_get_matched_publication_data_bad_parameter) +{ + PubSubReader reader(TEST_TOPIC_NAME); + PubSubWriter writer_1(TEST_TOPIC_NAME); + PubSubWriter writer_2(TEST_TOPIC_NAME); + + eprosima::fastdds::rtps::PublicationBuiltinTopicData pubdata; + + reader.reliability(eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS) + .init(); + + writer_1.reliability(eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS) + .init(); + writer_2.ownership_strength(10) + .init(); + + ASSERT_TRUE(reader.isInitialized()); + ASSERT_TRUE(writer_1.isInitialized()); + ASSERT_TRUE(writer_2.isInitialized()); + + // Reader should not be matched with any writer + reader.wait_discovery(std::chrono::seconds(2), 2); + + ASSERT_TRUE(!reader.is_matched()); + + auto& native_reader = reader.get_native_reader(); + + InstanceHandle_t w1_handle = writer_1.get_native_writer().get_instance_handle(); + ReturnCode_t ret = native_reader.get_matched_publication_data(pubdata, w1_handle); + + ASSERT_EQ(ret, eprosima::fastdds::dds::RETCODE_BAD_PARAMETER); + + InstanceHandle_t w2_handle = writer_2.get_native_writer().get_instance_handle(); + ret = native_reader.get_matched_publication_data(pubdata, w2_handle); + + ASSERT_EQ(ret, eprosima::fastdds::dds::RETCODE_BAD_PARAMETER); +} + +/** + * @test DDS-DR-API-GMPD-02 + * + * The operation must succeed when the publication is matched and correctly + * retrieve the publication data. Parameterize the test for different transports. + */ +TEST_P(DDSDataReader, datareader_get_matched_publication_data_correctly_behaves) +{ + PubSubReader reader(TEST_TOPIC_NAME); + PubSubWriter writer_1(TEST_TOPIC_NAME); + PubSubWriter writer_2(TEST_TOPIC_NAME); + + eprosima::fastdds::rtps::PublicationBuiltinTopicData w1_pubdata, w2_pubdata; + + reader.partition("*") + .init(); + + writer_1.partition("*") + .init(); + writer_2.user_data({'u', 's', 'e', 'r', 'd', 'a', 't', 'a'}) + .partition("*") + .reliability(eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS) + .init(); + + ASSERT_TRUE(reader.isInitialized()); + ASSERT_TRUE(writer_1.isInitialized()); + ASSERT_TRUE(writer_2.isInitialized()); + + // Reader must match with both writers + reader.wait_discovery(std::chrono::seconds::zero(), 2); + + ASSERT_EQ(reader.get_matched(), 2u); + + auto& native_reader = reader.get_native_reader(); + + InstanceHandle_t w1_handle = writer_1.get_native_writer().get_instance_handle(); + ReturnCode_t ret = native_reader.get_matched_publication_data(w1_pubdata, w1_handle); + + ASSERT_EQ(ret, eprosima::fastdds::dds::RETCODE_OK); + ASSERT_TRUE(validate_publication_builtin_topic_data(w1_pubdata, writer_1.get_native_writer())); + + InstanceHandle_t w2_handle = writer_2.get_native_writer().get_instance_handle(); + ret = native_reader.get_matched_publication_data(w2_pubdata, w2_handle); + + ASSERT_EQ(ret, eprosima::fastdds::dds::RETCODE_OK); + ASSERT_TRUE(validate_publication_builtin_topic_data(w2_pubdata, writer_2.get_native_writer())); +} + +/** + * @test DDS-DR-API-GMP-01 + * + * get_matched_publications() must return RETCODE_OK + * with an empty list if no DataWriters are matched. + */ +TEST(DDSDataReader, datareader_get_matched_publications_ok_empty_list) +{ + PubSubReader reader(TEST_TOPIC_NAME); + PubSubWriter writer_1(TEST_TOPIC_NAME); + PubSubWriter writer_2(TEST_TOPIC_NAME); + + std::vector pub_handles; + + reader.reliability(eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS) + .init(); + + writer_1.reliability(eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS) + .init(); + + writer_2.ownership_strength(10) + .init(); + + ASSERT_TRUE(reader.isInitialized()); + ASSERT_TRUE(writer_1.isInitialized()); + ASSERT_TRUE(writer_2.isInitialized()); + + // Reader should not be matched with any writer + reader.wait_discovery(std::chrono::seconds(2), 2); + ASSERT_FALSE(reader.is_matched()); + + auto& native_reader = reader.get_native_reader(); + ReturnCode_t ret = native_reader.get_matched_publications(pub_handles); + + ASSERT_EQ(ret, eprosima::fastdds::dds::RETCODE_OK); + ASSERT_EQ(pub_handles.size(), 0u); +} + +/** + * @test DDS-DR-API-GMP-02 + * + * get_matched_publications() must provide the correct list of matched publication handles. + * Parameterize the test for different transports. + */ +TEST_P(DDSDataReader, datareader_get_matched_publications_correctly_behaves) +{ + const size_t num_writers = 5; + + PubSubReader reader(TEST_TOPIC_NAME); + std::vector>> writers; + std::vector expected_pub_handles; + std::vector pub_handles; + + writers.reserve(num_writers); + pub_handles.reserve(num_writers); + + reader.reliability(eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS) + .init(); + + ASSERT_TRUE(reader.isInitialized()); + + for (size_t i = 0; i < num_writers; ++i) + { + writers.emplace_back(new PubSubWriter(TEST_TOPIC_NAME)); + writers.back()->init(); + ASSERT_TRUE(writers.back()->isInitialized()); + expected_pub_handles.emplace_back(writers.back()->get_native_writer().get_instance_handle()); + } + + // Wait for discovery + reader.wait_discovery(std::chrono::seconds::zero(), num_writers); + ASSERT_EQ(reader.get_matched(), num_writers); + + auto& native_reader = reader.get_native_reader(); + ReturnCode_t ret = native_reader.get_matched_publications(pub_handles); + + // Check that the list of matched publication handles is correct + ASSERT_EQ(ret, eprosima::fastdds::dds::RETCODE_OK); + ASSERT_EQ(pub_handles.size(), num_writers); + ASSERT_TRUE(std::is_permutation(pub_handles.begin(), pub_handles.end(), expected_pub_handles.begin())); + + // Remove two writers and check that the list of matched publication handles is updated + writers.pop_back(); + writers.pop_back(); + expected_pub_handles.pop_back(); + expected_pub_handles.pop_back(); + + // Wait for undiscovery + reader.wait_writer_undiscovery(static_cast(num_writers - 2)); + + pub_handles.clear(); + ret = native_reader.get_matched_publications(pub_handles); + ASSERT_EQ(ret, eprosima::fastdds::dds::RETCODE_OK); + ASSERT_EQ(pub_handles.size(), static_cast(num_writers - 2)); + ASSERT_TRUE(std::is_permutation(pub_handles.begin(), pub_handles.end(), expected_pub_handles.begin())); +} + +/** + * @test DDS-DR-API-GMP-03 + * + * The operation must provide the correct list of matched publication handles in multiple + * participants scenario. Parameterize the test for different transports. + */ +TEST_P(DDSDataReader, datareader_get_matched_publications_multiple_participants_correctly_behave) +{ + PubSubParticipant part_1(1, 1, 1, 1); + PubSubParticipant part_2(1, 1, 1, 1); + + part_1.pub_topic_name(TEST_TOPIC_NAME); + part_1.sub_topic_name(TEST_TOPIC_NAME + "_1"); + part_2.pub_topic_name(TEST_TOPIC_NAME + "_1"); + part_2.sub_topic_name(TEST_TOPIC_NAME); + + ASSERT_TRUE(part_1.init_participant()); + ASSERT_TRUE(part_1.init_publisher(0)); + ASSERT_TRUE(part_1.init_subscriber(0)); + + ASSERT_TRUE(part_2.init_participant()); + ASSERT_TRUE(part_2.init_subscriber(0)); + ASSERT_TRUE(part_2.init_publisher(0)); + + part_1.pub_wait_discovery(); + part_1.sub_wait_discovery(); + + part_2.pub_wait_discovery(); + part_2.sub_wait_discovery(); + + auto& reader_p1 = part_1.get_native_reader(0); + auto& reader_p2 = part_2.get_native_reader(0); + + std::vector pub_handles_p1; + std::vector pub_handles_p2; + + ReturnCode_t ret = reader_p1.get_matched_publications(pub_handles_p1); + ASSERT_EQ(ret, eprosima::fastdds::dds::RETCODE_OK); + ASSERT_EQ(pub_handles_p1.size(), 1u); + ASSERT_EQ(pub_handles_p1[0], part_2.get_native_writer(0).get_instance_handle()); + + ret = reader_p2.get_matched_publications(pub_handles_p2); + ASSERT_EQ(ret, eprosima::fastdds::dds::RETCODE_OK); + ASSERT_EQ(pub_handles_p2.size(), 1u); + ASSERT_EQ(pub_handles_p2[0], part_1.get_native_writer(0).get_instance_handle()); +} + +// This tests checks that non-default QoS are correctly sent in the Data(r) +// Only half of the QoS are modified, so the other half should be the default ones and they should not +// be sent. The opposite scenario will be checked in the following test. +// QoS that should be sent: +// - DurabilityQosPolicy +// - DeadlineQosPolicy +// - LatencyBudgetQosPolicy +// - LivelinessQosPolicy +// - ReliabilityQosPolicy +// - LifespanQosPolicy +// - UserDataQosPolicy +// - TimeBasedFilterQosPolicy +// - OwnershipQosPolicy +// - DestinationOrderQosPolicy can NOT be tested, as it is not implemented yet so the default value cannot be modified +TEST_P(DDSDataReader, datareader_sends_non_default_qos_a) +{ + if (TRANSPORT != GetParam()) + { + GTEST_SKIP() << "Only makes sense on TRANSPORT"; + return; + } + + std::atomic qos_found { 0 }; + std::vector expected_qos_pids = { + eprosima::fastdds::dds::PID_DURABILITY, + eprosima::fastdds::dds::PID_DEADLINE, + eprosima::fastdds::dds::PID_LATENCY_BUDGET, + eprosima::fastdds::dds::PID_LIVELINESS, + eprosima::fastdds::dds::PID_RELIABILITY, + eprosima::fastdds::dds::PID_LIFESPAN, + eprosima::fastdds::dds::PID_USER_DATA, + eprosima::fastdds::dds::PID_TIME_BASED_FILTER, + eprosima::fastdds::dds::PID_OWNERSHIP, + }; + const uint8_t expected_qos_size = static_cast(expected_qos_pids.size()); + + PubSubWriter writer(TEST_TOPIC_NAME); + PubSubReader reader(TEST_TOPIC_NAME); + + auto test_transport = std::make_shared(); + test_transport->drop_builtin_data_messages_filter_ = [&](rtps::CDRMessage_t& msg) + { + return check_qos_in_data_r(msg, qos_found, expected_qos_pids); + }; + + // Modify writer's QoS just to match the reader's + eprosima::fastdds::dds::DataWriterQos dw_qos; + dw_qos.durability().kind = eprosima::fastdds::dds::TRANSIENT_LOCAL_DURABILITY_QOS; + dw_qos.deadline().period = { 7, 0 }; // Lower than reader's + dw_qos.latency_budget().duration = { 7, 0 }; // Lower than reader's + dw_qos.liveliness().lease_duration = { 7, 0 }; // Lower than reader's + dw_qos.liveliness().announcement_period = { 5, 0 }; // Lower than writer's lease duration + dw_qos.reliability().kind = eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS; + dw_qos.ownership().kind = eprosima::fastdds::dds::EXCLUSIVE_OWNERSHIP_QOS; + dw_qos.data_sharing().off(); + + eprosima::fastdds::dds::DataReaderQos dr_qos; + dr_qos.durability().kind = eprosima::fastdds::dds::TRANSIENT_LOCAL_DURABILITY_QOS; + dr_qos.deadline().period = { 42, 0 }; + dr_qos.latency_budget().duration = { 42, 0 }; + dr_qos.liveliness().lease_duration = { 42, 0 }; + dr_qos.liveliness().announcement_period = { 39, 0 }; + dr_qos.reliability().kind = eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS; + dr_qos.lifespan().duration = { 42, 0 }; + std::vector vec; + eprosima::fastdds::rtps::octet val = 42; + vec.push_back(val); + dr_qos.user_data().data_vec(vec); + dr_qos.time_based_filter().minimum_separation = { 42, 0 }; + dr_qos.ownership().kind = eprosima::fastdds::dds::EXCLUSIVE_OWNERSHIP_QOS; + dr_qos.destination_order().kind = eprosima::fastdds::dds::BY_RECEPTION_TIMESTAMP_DESTINATIONORDER_QOS; + dr_qos.data_sharing().off(); + + writer.data_writer_qos(dw_qos); + reader.disable_builtin_transport() + .add_user_transport_to_pparams(test_transport) + .data_reader_qos(dr_qos); + + writer.init(); + reader.init(); + ASSERT_TRUE(writer.isInitialized()); + ASSERT_TRUE(reader.isInitialized()); + + writer.wait_discovery(); + reader.wait_discovery(); + + EXPECT_EQ(qos_found.load(), expected_qos_size); + EXPECT_EQ(expected_qos_pids.size(), 0u); +} + +// This tests checks that non-default QoS are correctly sent in the Data(r) +// Only half of the QoS are modified, so the other half should be the default ones and they should not +// be sent. The opposite scenario will be checked in the previous test. +// QoS that should be sent: +// - DurabilityQosPolicy (always sent) +// - PresentationQosPolicy +// - PartitionQosPolicy +// - GroupDataQosPolicy +// - DisablePositiveACKsQosPolicy +// - TypeConsistencyEnforcementQosPolicy +// - DataSharingQosPolicy +// - DataRepresentationQosPolicy +// - HistoryQosPolicy +TEST_P(DDSDataReader, datareader_sends_non_default_qos_b) +{ + if (TRANSPORT != GetParam()) + { + GTEST_SKIP() << "Only makes sense on TRANSPORT"; + return; + } + + std::atomic qos_found { 0 }; + std::vector expected_qos_pids = { + eprosima::fastdds::dds::PID_DURABILITY, + eprosima::fastdds::dds::PID_PRESENTATION, + eprosima::fastdds::dds::PID_PARTITION, + eprosima::fastdds::dds::PID_GROUP_DATA, + eprosima::fastdds::dds::PID_DISABLE_POSITIVE_ACKS, + eprosima::fastdds::dds::PID_TYPE_CONSISTENCY_ENFORCEMENT, + eprosima::fastdds::dds::PID_DATASHARING, + eprosima::fastdds::dds::PID_DATA_REPRESENTATION, + eprosima::fastdds::dds::PID_HISTORY + }; + const uint8_t expected_qos_size = static_cast(expected_qos_pids.size()); + + PubSubWriter writer(TEST_TOPIC_NAME); + PubSubReader reader(TEST_TOPIC_NAME); + + auto test_transport = std::make_shared(); + test_transport->drop_builtin_data_messages_filter_ = [&](rtps::CDRMessage_t& msg) + { + return check_qos_in_data_r(msg, qos_found, expected_qos_pids); + }; + + // Modify writer's QoS just to match the reader's + eprosima::fastdds::dds::PublisherQos pub_qos; + pub_qos.partition().push_back("partition_1"); + eprosima::fastdds::dds::DataWriterQos dw_qos; + dw_qos.durability().kind = eprosima::fastdds::dds::TRANSIENT_LOCAL_DURABILITY_QOS; + dw_qos.reliability().kind = eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS; + dw_qos.reliable_writer_qos().disable_positive_acks.enabled = true; + + eprosima::fastdds::dds::SubscriberQos sub_qos; + sub_qos.presentation().access_scope = eprosima::fastdds::dds::GROUP_PRESENTATION_QOS; + sub_qos.partition().push_back("partition_1"); + std::vector vec; + eprosima::fastdds::rtps::octet val = 42; + vec.push_back(val); + sub_qos.group_data().data_vec(vec); + eprosima::fastdds::dds::DataReaderQos dr_qos; + dr_qos.reliable_reader_qos().disable_positive_acks.enabled = true; + dr_qos.reliable_reader_qos().disable_positive_acks.duration = { 42, 0 }; + dr_qos.representation().m_value.push_back(eprosima::fastdds::dds::DataRepresentationId_t::XCDR_DATA_REPRESENTATION); + dr_qos.type_consistency().m_kind = eprosima::fastdds::dds::DISALLOW_TYPE_COERCION; + dr_qos.history().kind = eprosima::fastdds::dds::KEEP_LAST_HISTORY_QOS; + dr_qos.history().depth = 42; + +#ifdef _WIN32 + dw_qos.data_sharing().on("c:\\programdata\\eprosima\\fastdds_interprocess\\"); + dr_qos.data_sharing().on("c:\\programdata\\eprosima\\fastdds_interprocess\\"); +#elif __APPLE__ + dw_qos.data_sharing().on("/private/tmp/boost_interprocess/"); + dr_qos.data_sharing().on("/private/tmp/boost_interprocess/"); +#elif __linux__ + dw_qos.data_sharing().on("/dev/shm"); + dr_qos.data_sharing().on("/dev/shm"); +#else + throw std::runtime_error(std::string("Platform not supported")); +#endif // ifdef _WIN32 + + writer.publisher_qos(pub_qos) + .data_writer_qos(dw_qos); + reader.disable_builtin_transport() + .add_user_transport_to_pparams(test_transport) + .subscriber_qos(sub_qos) + .data_reader_qos(dr_qos); + + writer.init(); + reader.init(); + ASSERT_TRUE(writer.isInitialized()); + ASSERT_TRUE(reader.isInitialized()); + + writer.wait_discovery(); + reader.wait_discovery(); + + EXPECT_EQ(qos_found.load(), expected_qos_size); + EXPECT_EQ(expected_qos_pids.size(), 0u); +} + +// This tests checks that non-default optional QoS are correctly sent in the Data(r) +// QoS that should be sent: +// - ResourceLimitsQosPolicy +// - ReaderDataLifecycleQosPolicy +// - RTPSReliableReaderQos +// - RTPSEndpointQos +// - ReaderResourceLimitsQos +// a) The test is run with the property set to false, so the optional QoS are not serialized. +// b) The test is run with the property set to true, so the optional QoS are serialized. +// c) The test is run with the default QoS and the property set to true, so the optional QoS are not serialized. +TEST_P(DDSDataReader, datareader_sends_non_default_qos_optional) +{ + if (TRANSPORT != GetParam()) + { + GTEST_SKIP() << "Only makes sense on TRANSPORT"; + return; + } + + std::atomic qos_found { 0 }; + std::vector expected_qos_pids = { + eprosima::fastdds::dds::PID_RESOURCE_LIMITS, + eprosima::fastdds::dds::PID_READER_DATA_LIFECYCLE, + eprosima::fastdds::dds::PID_RTPS_RELIABLE_READER, + eprosima::fastdds::dds::PID_RTPS_ENDPOINT, + eprosima::fastdds::dds::PID_READER_RESOURCE_LIMITS, + }; + const uint8_t expected_qos_size = static_cast(expected_qos_pids.size()); + + PubSubWriter writer(TEST_TOPIC_NAME); + PubSubReader reader(TEST_TOPIC_NAME); + + auto test_transport = std::make_shared(); + test_transport->drop_builtin_data_messages_filter_ = [&](rtps::CDRMessage_t& msg) + { + return check_qos_in_data_r(msg, qos_found, expected_qos_pids); + }; + + // Default writer's QoS + eprosima::fastdds::dds::DataWriterQos dw_qos = eprosima::fastdds::dds::DATAWRITER_QOS_DEFAULT; + dw_qos.data_sharing().off(); + + eprosima::fastdds::dds::DataReaderQos dr_qos; + dr_qos.resource_limits().max_samples = 1000; + dr_qos.reader_data_lifecycle().autopurge_disposed_samples_delay = { 4, 0 }; + dr_qos.reliable_reader_qos().times.initial_acknack_delay = { 4, 0 }; + dr_qos.endpoint().entity_id = 42; + dr_qos.reader_resource_limits().matched_publisher_allocation.initial = 1; + dr_qos.data_sharing().off(); + + writer.data_writer_qos(dw_qos); + reader.disable_builtin_transport() + .add_user_transport_to_pparams(test_transport) + .data_reader_qos(dr_qos); + + // a) Init both entities without setting the property + writer.init(); + reader.init(); + ASSERT_TRUE(writer.isInitialized()); + ASSERT_TRUE(reader.isInitialized()); + + writer.wait_discovery(); + reader.wait_discovery(); + + // No optional QoS should be sent. Only PID_DURABILITY as it is always sent + EXPECT_EQ(qos_found.load(), 1u); + EXPECT_EQ(expected_qos_pids.size(), expected_qos_size); + + // b) Now set the property to serialize optional QoS and re-init the reader + reader.destroy(); + writer.wait_reader_undiscovery(); + qos_found.store(0); + + eprosima::fastdds::dds::PropertyPolicyQos properties; + properties.properties().emplace_back("fastdds.serialize_optional_qos", "true"); + reader.property_policy(properties); + + reader.init(); + ASSERT_TRUE(reader.isInitialized()); + + writer.wait_discovery(); + reader.wait_discovery(); + + // Check that the optional QoS are serialized + PID_DURABILITY as it is always sent + EXPECT_EQ(qos_found.load(), expected_qos_size + 1u); + EXPECT_EQ(expected_qos_pids.size(), 0u); + + // c) Now re-init the reader with default QoS and the property set + reader.destroy(); + writer.wait_reader_undiscovery(); + qos_found.store(0); + + dr_qos = eprosima::fastdds::dds::DATAREADER_QOS_DEFAULT; + dr_qos.data_sharing().off(); + + reader.data_reader_qos(dr_qos) + .init(); + ASSERT_TRUE(reader.isInitialized()); + + writer.wait_discovery(); + reader.wait_discovery(); + + // Check that no optional QoS are serialized. Only PID_DURABILITY as it is always sent + EXPECT_EQ(qos_found.load(), 1u); +} + +// This is a regression test to check the reception time used when Samples are lost and need to be resent. +TEST(DDSDataReader, reception_timestamp_for_resent_samples) +{ + using namespace eprosima::fastdds::dds; + + // A reliable Pub-Sub scenario will be created. + // One sample will be filtered out to force the publisher to resend it. + // The reception timestamp of the sample will be checked. + + class CustomPubSubReader : public PubSubReader + { + public: + + CustomPubSubReader( + const std::string& topic_name) + : PubSubReader(topic_name) + { + } + + std::map reception_timestamps; + + private: + + void postprocess_sample( + const type& sample, + const SampleInfo& info) override final + { + if (info.valid_data) + { + reception_timestamps[sample.index()] = info.reception_timestamp; + std::cout << "Sample " << sample.index() << " received at " + << info.reception_timestamp.seconds() << "." << info.reception_timestamp.nanosec() + << std::endl; + } + } + + }; + + std::atomic filter_activated { false }; + auto block_data_msgs = [&filter_activated](CDRMessage_t& msg) + { + // Filter Data messages + if (filter_activated.load(std::memory_order::memory_order_seq_cst)) + { + uint32_t old_pos = msg.pos; + + SequenceNumber_t sn; + + msg.pos += 2; // Flags + msg.pos += 2; // Octets to inline QoS + msg.pos += 4; // Reader ID + msg.pos += 4; // Writer ID + sn.high = (int32_t)eprosima::fastdds::helpers::cdr_parse_u32( + (char*)&msg.buffer[msg.pos]); + msg.pos += 4; + sn.low = eprosima::fastdds::helpers::cdr_parse_u32( + (char*)&msg.buffer[msg.pos]); + + // Restore buffer pos + msg.pos = old_pos; + + // Filter only first Data sent with Sequence number 0-1 + if (sn == SequenceNumber_t{0, 1}) + { + std::cout << "Blocking Data msg of Sequence number 0-1." << std::endl; + return true; + } + } + return false; + }; + + // Declare a test transport that will block DATA msgs sent + auto test_transport = std::make_shared(); + test_transport->drop_data_messages_filter_ = [&](CDRMessage_t& msg) + { + return block_data_msgs(msg); + }; + + PubSubWriter writer(TEST_TOPIC_NAME); + CustomPubSubReader reader(TEST_TOPIC_NAME); + + // The writer will use the test transport. Both reliable and history depth will be set to 5. + writer.disable_builtin_transport() + .add_user_transport_to_pparams(test_transport) + .reliability(ReliabilityQosPolicyKind::RELIABLE_RELIABILITY_QOS) + .history_kind(KEEP_LAST_HISTORY_QOS) + .history_depth(3) + .init(); + reader.setup_transports(eprosima::fastdds::rtps::BuiltinTransports::UDPv4) + .reliability(ReliabilityQosPolicyKind::RELIABLE_RELIABILITY_QOS) + .history_kind(KEEP_LAST_HISTORY_QOS) + .history_depth(3) + .init(); + + ASSERT_TRUE(writer.isInitialized()); + ASSERT_TRUE(reader.isInitialized()); + + // Wait for discovery + writer.wait_discovery(); + reader.wait_discovery(); + + // Activate the filter and send first sample + filter_activated.store(true, std::memory_order::memory_order_seq_cst); + + auto data = default_helloworld_data_generator(3); + reader.startReception(data); + + auto samples_it = data.begin(); + writer.send_sample(*samples_it); + // Ensure that the sample has not been received yet + ASSERT_EQ(reader.block_for_all(std::chrono::seconds(1)), 0u); + + // Send the rest of the samples and then deactivate the filter + ++samples_it; + for (; samples_it != data.end(); ++samples_it) + { + writer.send_sample(*samples_it); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + filter_activated.store(false, std::memory_order::memory_order_seq_cst); + // Wait for the reception of all samples + ASSERT_EQ(reader.block_for_all(std::chrono::seconds(5)), 3u); + + // Check timestamps. reception_timestamps map is accesed by index of HelloWorld data + ASSERT_EQ(reader.reception_timestamps.size(), 3u); + auto reception_ts_1 = reader.reception_timestamps[1]; + auto reception_ts_2 = reader.reception_timestamps[2]; + auto reception_ts_3 = reader.reception_timestamps[3]; + EXPECT_TRUE(reception_ts_1 <= reception_ts_2); + EXPECT_TRUE(reception_ts_2 <= reception_ts_3); +} + +/* This is a regression test for redmine issue 22929. + * + * Considers the following scenario: + * - A DataReader is created on keyed topic A + * - A DataWriter is created on the same topic + * - DataWriter writes sample 1 to instance 1 + * - DataReader takes sample 1 + * - DataWriter is deleted + * + * The following behavior is expected: + * - Calling take on the DataReader returns a sample on instance 1 with + * valid_data = false to inform about the change in the instance state + * to NOT_ALIVE_NO_WRITERS + */ +TEST_P(DDSDataReader, return_sample_when_writer_disappears) +{ + namespace fdds = eprosima::fastdds::dds; + + struct CustomReaderListener : public fdds::DataReaderListener + { + void on_data_available( + fdds::DataReader* /* reader */) override + { + inc_data_available_count(); + } + + void on_subscription_matched( + fdds::DataReader* /* reader */, + const fdds::SubscriptionMatchedStatus& info) override + { + set_current_matched(info.current_count); + } + + size_t get_data_available_count() const + { + std::lock_guard lock(mutex_); + return data_available_count_; + } + + void wait_for_match() + { + std::unique_lock lock(mutex_); + cv_.wait(lock, + [this]() + { + return current_matched_ > 0; + }); + } + + void wait_for_unmatch() + { + std::unique_lock lock(mutex_); + cv_.wait(lock, + [this]() + { + return current_matched_ == 0; + }); + } + + private: + + void inc_data_available_count() + { + std::lock_guard lock(mutex_); + ++data_available_count_; + } + + void set_current_matched( + int32_t current_count) + { + std::lock_guard lock(mutex_); + current_matched_ = current_count; + cv_.notify_all(); + } + + mutable std::mutex mutex_; + std::condition_variable cv_; + size_t data_available_count_ = 0; + int32_t current_matched_ = 0; + }; + + fdds::InstanceHandle_t instance_handle{}; + CustomReaderListener listener; + + // Create a DataReader on a keyed topic + PubSubReader reader(TEST_TOPIC_NAME); + reader.reliability(eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS) + .durability_kind(eprosima::fastdds::dds::TRANSIENT_LOCAL_DURABILITY_QOS) + .history_kind(eprosima::fastdds::dds::KEEP_LAST_HISTORY_QOS) + .history_depth(1) + .init(); + ASSERT_TRUE(reader.isInitialized()); + fdds::DataReader& data_reader = reader.get_native_reader(); + data_reader.set_listener(&listener, fdds::StatusMask::all()); + + // Create a DataWriter on the same topic + PubSubWriter writer(TEST_TOPIC_NAME); + writer.reliability(eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS) + .durability_kind(eprosima::fastdds::dds::TRANSIENT_LOCAL_DURABILITY_QOS) + .history_kind(eprosima::fastdds::dds::KEEP_LAST_HISTORY_QOS) + .history_depth(1) + .init(); + ASSERT_TRUE(writer.isInitialized()); + + // Wait for discovery + writer.wait_discovery(); + listener.wait_for_match(); + + // DataWriter writes sample 1 to instance 1 + { + KeyedHelloWorldPubSubType::type sample; + sample.key(1); + sample.index(1); + sample.message("Hello World"); + EXPECT_TRUE(writer.send_sample(sample)); + } + + // DataReader takes sample 1 + { + EXPECT_TRUE(data_reader.wait_for_unread_message(fdds::c_TimeInfinite)); + EXPECT_TRUE(data_reader.get_status_changes().is_active(fdds::StatusMask::data_available())); + EXPECT_EQ(listener.get_data_available_count(), 1u); + + fdds::SampleInfo untaken_info; + EXPECT_EQ(data_reader.get_first_untaken_info(&untaken_info), fdds::RETCODE_OK); + + EXPECT_TRUE(untaken_info.valid_data); + EXPECT_EQ(untaken_info.instance_state, fdds::ALIVE_INSTANCE_STATE); + + fdds::SampleInfo info; + KeyedHelloWorldPubSubType::type sample; + EXPECT_EQ(data_reader.take_next_sample(&sample, &info), fdds::RETCODE_OK); + EXPECT_FALSE(data_reader.get_status_changes().is_active(fdds::StatusMask::data_available())); + + EXPECT_TRUE(info.valid_data); + EXPECT_EQ(info.instance_state, fdds::ALIVE_INSTANCE_STATE); + EXPECT_EQ(sample.key(), 1); + EXPECT_EQ(sample.index(), 1); + EXPECT_EQ(sample.message(), "Hello World"); + + // Store the instance handle for later use + instance_handle = info.instance_handle; + } + + // DataWriter is deleted + writer.destroy(); + listener.wait_for_unmatch(); + + // Verify expectations + { + EXPECT_TRUE(data_reader.get_status_changes().is_active(fdds::StatusMask::data_available())); + EXPECT_EQ(listener.get_data_available_count(), 2u); + + fdds::SampleInfo untaken_info; + EXPECT_EQ(data_reader.get_first_untaken_info(&untaken_info), fdds::RETCODE_OK); + + EXPECT_FALSE(untaken_info.valid_data); + EXPECT_EQ(untaken_info.instance_handle, instance_handle); + EXPECT_EQ(untaken_info.instance_state, fdds::NOT_ALIVE_NO_WRITERS_INSTANCE_STATE); + + fdds::SampleInfo info; + KeyedHelloWorldPubSubType::type sample; + + EXPECT_EQ(data_reader.take_next_sample(&sample, &info), fdds::RETCODE_OK); + EXPECT_FALSE(data_reader.get_status_changes().is_active(fdds::StatusMask::data_available())); + + EXPECT_FALSE(info.valid_data); + EXPECT_EQ(info.instance_handle, instance_handle); + EXPECT_EQ(info.instance_state, fdds::NOT_ALIVE_NO_WRITERS_INSTANCE_STATE); + } +} + +>>>>>>> 25a43a7c3 (Add UBSan workflow and solve its errors (#6386)) #ifdef INSTANTIATE_TEST_SUITE_P #define GTEST_INSTANTIATE_TEST_MACRO(x, y, z, w) INSTANTIATE_TEST_SUITE_P(x, y, z, w) #else @@ -439,4 +1436,3 @@ GTEST_INSTANTIATE_TEST_MACRO(DDSDataReader, } }); - diff --git a/test/blackbox/common/DDSBlackboxTestsDataWriter.cpp b/test/blackbox/common/DDSBlackboxTestsDataWriter.cpp index be0fc5d76d1..fa18cd1935c 100644 --- a/test/blackbox/common/DDSBlackboxTestsDataWriter.cpp +++ b/test/blackbox/common/DDSBlackboxTestsDataWriter.cpp @@ -29,12 +29,14 @@ using namespace eprosima::fastrtps; using test_UDPv4Transport = eprosima::fastdds::rtps::test_UDPv4Transport; using test_UDPv4TransportDescriptor = eprosima::fastdds::rtps::test_UDPv4TransportDescriptor; +namespace { enum communication_type { TRANSPORT, INTRAPROCESS, DATASHARING }; +} // namespace class DDSDataWriter : public testing::TestWithParam { @@ -350,6 +352,1554 @@ TEST(DDSDataWriter, default_qos_large_history_depth) ASSERT_TRUE(writer.isInitialized()); } +<<<<<<< HEAD +======= +/** + * Utility class to set some values other than default to those Qos common to Topic and DataWriter. + * + * This is a class instead of a free function to avoid linking with its TestsDataReader counterpart. + */ +class TestsDataWriterQosCommonUtils +{ +public: + + template + static void set_common_qos( + T& qos) + { + qos.durability_service().history_kind = eprosima::fastdds::dds::KEEP_ALL_HISTORY_QOS; + qos.reliability().kind = eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS; + qos.durability().kind = eprosima::fastdds::dds::VOLATILE_DURABILITY_QOS; + qos.deadline().period = {0, 500000000}; + qos.latency_budget().duration = 0; + qos.liveliness().kind = eprosima::fastdds::dds::MANUAL_BY_PARTICIPANT_LIVELINESS_QOS; + qos.resource_limits().max_samples = 1000; + qos.transport_priority().value = 1; + qos.ownership().kind = eprosima::fastdds::dds::EXCLUSIVE_OWNERSHIP_QOS; + qos.representation().m_value.push_back( + eprosima::fastdds::dds::DataRepresentationId_t::XCDR2_DATA_REPRESENTATION); + qos.history().kind = eprosima::fastdds::dds::KEEP_ALL_HISTORY_QOS; + qos.lifespan().duration = {5, 0}; + } + +}; + +/* + * This test: + * 1. Creates a Topic with custom Qos + * 2. Updates the default DataWriter Qos that are not in common with Topic Qos with non-default values + * 3. Creates a DataWriter with DATAWRITER_QOS_USE_TOPIC_QOS + * 4. Checks that the used Qos are the merge between the default ones and the Topic ones + */ +TEST(DDSDataWriter, datawriter_qos_use_topic_qos) +{ + using namespace eprosima::fastdds::dds; + + /* Create a topic with custom Qos */ + // Set Topic Qos different from default + TopicQos topic_qos; + TestsDataWriterQosCommonUtils::set_common_qos(topic_qos); + + // Create DomainParticipant + DomainParticipant* participant = + DomainParticipantFactory::get_instance()->create_participant(0, PARTICIPANT_QOS_DEFAULT); + ASSERT_NE(participant, nullptr); + + /* Create a DataWriter with modified default Qos using the Topic Qos */ + // Create Topic + TypeSupport type_support; + type_support.reset(new HelloWorldPubSubType()); + type_support.register_type(participant, "HelloWorld"); + Topic* topic = participant->create_topic("HelloWorldTopic", "HelloWorld", topic_qos); + + // Create the Publisher + Publisher* publisher = participant->create_publisher(PUBLISHER_QOS_DEFAULT); + ASSERT_NE(publisher, nullptr); + + // Change default DataWriter Qos (only those that are different from Topic Qos) + DataWriterQos control_qos; + control_qos.ownership_strength().value = 1; + control_qos.publish_mode().kind = eprosima::fastdds::dds::ASYNCHRONOUS_PUBLISH_MODE; + control_qos.writer_data_lifecycle().autodispose_unregistered_instances = false; + control_qos.user_data().push_back(0); + control_qos.endpoint().entity_id = 1; + control_qos.writer_resource_limits().matched_subscriber_allocation = + ResourceLimitedContainerConfig::fixed_size_configuration(1u); + control_qos.data_sharing().off(); + publisher->set_default_datawriter_qos(control_qos); + + // Create DataWriter with DATAREADER_QOS_USE_TOPIC_QOS + DataWriter* writer = publisher->create_datawriter(topic, DATAWRITER_QOS_USE_TOPIC_QOS); + ASSERT_NE(writer, nullptr); + + /* Check that used Qos are the merge between the default ones and the Topic ones */ + // Set the topic values on the control DataWriterQos + TestsDataWriterQosCommonUtils::set_common_qos(control_qos); + + // Get used DataWriter Qos + DataWriterQos test_qos = writer->get_qos(); + + // Check that the Qos that are not in common with Topic Qos are correctly set as the default ones, + // and that the rest of the Qos are left unmodified + ASSERT_EQ(control_qos, test_qos); +} + +bool validate_subscription_builtin_topic_data( + const eprosima::fastdds::rtps::SubscriptionBuiltinTopicData& subdata, + const eprosima::fastdds::dds::DataReader& datareader) +{ + bool ret = true; + + auto dr_qos = datareader.get_qos(); + auto sub_qos = datareader.get_subscriber()->get_qos(); + + eprosima::fastdds::rtps::BuiltinTopicKey_t dr_key, part_key; + + entity_id_to_builtin_topic_key(dr_key, datareader.guid().entityId); + guid_prefix_to_builtin_topic_key(part_key, datareader.get_subscriber()->get_participant()->guid().guidPrefix); + + ret &= (0 == memcmp(subdata.key.value, dr_key.value, sizeof(eprosima::fastdds::rtps::BuiltinTopicKey_t))); + ret &= + (0 == + memcmp(subdata.participant_key.value, part_key.value, + sizeof(eprosima::fastdds::rtps::BuiltinTopicKey_t))); + ret &= (subdata.topic_name == datareader.get_topicdescription()->get_name()); + ret &= (subdata.type_name == datareader.get_topicdescription()->get_type_name()); + + // DataReader Qos + ret &= (subdata.durability == dr_qos.durability()); + ret &= (subdata.deadline == dr_qos.deadline()); + ret &= (subdata.latency_budget == dr_qos.latency_budget()); + ret &= (subdata.liveliness == dr_qos.liveliness()); + ret &= (subdata.reliability == dr_qos.reliability()); + ret &= (subdata.ownership == dr_qos.ownership()); + ret &= (subdata.destination_order == dr_qos.destination_order()); + ret &= ( + (subdata.user_data.size() == dr_qos.user_data().size()) && + (subdata.user_data.size() == 0 || + 0 == memcmp(subdata.user_data.data(), dr_qos.user_data().data(), subdata.user_data.size()))); + // time based filter not implemented + + // Subscriber Qos + ret &= (subdata.presentation == sub_qos.presentation()); + ret &= (subdata.partition.getNames() == sub_qos.partition().getNames()); + // topic_data not implemented + // group_data too + + return ret; +} + +/** + * @test DDS-DW-API-GMSD-01 + * + * get_matched_subscription_data() must return RETCODE_BAD_PARAMETER + * if the subscription is not matched. + */ +TEST(DDSDataWriter, datawriter_get_matched_subscription_data_bad_parameter) +{ + using InstanceHandle_t = eprosima::fastdds::rtps::InstanceHandle_t; + + PubSubWriter writer(TEST_TOPIC_NAME); + PubSubReader reader_1(TEST_TOPIC_NAME); + PubSubReader reader_2(TEST_TOPIC_NAME); + + eprosima::fastdds::rtps::SubscriptionBuiltinTopicData subdata; + + writer.reliability(eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS) + .init(); + + reader_1.reliability(eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS) + .init(); + reader_2.ownership_exclusive() + .init(); + + ASSERT_TRUE(writer.isInitialized()); + ASSERT_TRUE(reader_1.isInitialized()); + ASSERT_TRUE(reader_2.isInitialized()); + + // Writer should not be matched with any reader + writer.wait_discovery(2, std::chrono::seconds(1)); + + ASSERT_TRUE(!writer.is_matched()); + + auto& native_writer = writer.get_native_writer(); + + InstanceHandle_t r1_handle = reader_1.get_native_reader().get_instance_handle(); + ReturnCode_t ret = native_writer.get_matched_subscription_data(subdata, r1_handle); + + ASSERT_EQ(ret, eprosima::fastdds::dds::RETCODE_BAD_PARAMETER); + + InstanceHandle_t r2_handle = reader_2.get_native_reader().get_instance_handle(); + ret = native_writer.get_matched_subscription_data(subdata, r2_handle); + + ASSERT_EQ(ret, eprosima::fastdds::dds::RETCODE_BAD_PARAMETER); +} + +/** + * @test DDS-DW-API-GMSD-02 + * + * The operation must succeed when the subscription is matched and correctly + * retrieve the publication data. Parameterize the test for different transports. + */ +TEST_P(DDSDataWriter, datawriter_get_matched_subscription_data_correctly_behaves) +{ + using InstanceHandle_t = eprosima::fastdds::rtps::InstanceHandle_t; + + PubSubWriter writer(TEST_TOPIC_NAME); + PubSubReader reader_1(TEST_TOPIC_NAME); + PubSubReader reader_2(TEST_TOPIC_NAME); + + eprosima::fastdds::rtps::SubscriptionBuiltinTopicData r1_subdata, r2_subdata; + + writer.partition("*") + .init(); + + reader_1.partition("*") + .init(); + reader_2.user_data({'u', 's', 'e', 'r', 'd', 'a', 't', 'a'}) + .partition("*") + .reliability(eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS) + .init(); + + ASSERT_TRUE(writer.isInitialized()); + ASSERT_TRUE(reader_1.isInitialized()); + ASSERT_TRUE(reader_2.isInitialized()); + + // Writer must match with both readers + writer.wait_discovery(2, std::chrono::seconds::zero()); + + ASSERT_EQ(writer.get_matched(), 2u); + + auto& native_writer = writer.get_native_writer(); + + InstanceHandle_t r1_handle = reader_1.get_native_reader().get_instance_handle(); + ReturnCode_t ret = native_writer.get_matched_subscription_data(r1_subdata, r1_handle); + + ASSERT_EQ(ret, eprosima::fastdds::dds::RETCODE_OK); + ASSERT_TRUE(validate_subscription_builtin_topic_data(r1_subdata, reader_1.get_native_reader())); + + InstanceHandle_t r2_handle = reader_2.get_native_reader().get_instance_handle(); + ret = native_writer.get_matched_subscription_data(r2_subdata, r2_handle); + + ASSERT_EQ(ret, eprosima::fastdds::dds::RETCODE_OK); + ASSERT_TRUE(validate_subscription_builtin_topic_data(r2_subdata, reader_2.get_native_reader())); +} + +/** + * @test DDS-DW-API-GMS-01 + * + * get_matched_subscriptions() must return RETCODE_OK + * with an empty list if no DataWriters are matched. + */ +TEST(DDSDataWriter, datawriter_get_matched_subscriptions_ok_empty_list) +{ + using InstanceHandle_t = eprosima::fastdds::rtps::InstanceHandle_t; + + PubSubWriter writer(TEST_TOPIC_NAME); + PubSubReader reader_1(TEST_TOPIC_NAME); + PubSubReader reader_2(TEST_TOPIC_NAME); + + std::vector sub_handles; + + writer.reliability(eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS) + .init(); + + reader_1.reliability(eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS) + .init(); + + reader_2.ownership_exclusive() + .init(); + + ASSERT_TRUE(writer.isInitialized()); + ASSERT_TRUE(reader_1.isInitialized()); + ASSERT_TRUE(reader_2.isInitialized()); + + // Writer should not be matched with any reader + writer.wait_discovery(2, std::chrono::seconds(2)); + ASSERT_FALSE(writer.is_matched()); + + auto& native_writer = writer.get_native_writer(); + ReturnCode_t ret = native_writer.get_matched_subscriptions(sub_handles); + + ASSERT_EQ(ret, eprosima::fastdds::dds::RETCODE_OK); + ASSERT_EQ(sub_handles.size(), 0u); +} + +/** + * @test DDS-DW-API-GMS-02 + * + * get_matched_subscriptions() must provide the correct list of matched subscription handles. + * Parameterize the test for different transports. + */ +TEST_P(DDSDataWriter, datawriter_get_matched_subscriptions_correctly_behaves) +{ + using InstanceHandle_t = eprosima::fastdds::rtps::InstanceHandle_t; + + const size_t num_readers = 5; + + PubSubWriter writer(TEST_TOPIC_NAME); + std::vector>> readers; + std::vector expected_sub_handles; + std::vector sub_handles; + + readers.reserve(num_readers); + sub_handles.reserve(num_readers); + + writer.init(); + + ASSERT_TRUE(writer.isInitialized()); + + for (size_t i = 0; i < num_readers; ++i) + { + readers.emplace_back(new PubSubReader(TEST_TOPIC_NAME)); + readers.back()->init(); + ASSERT_TRUE(readers.back()->isInitialized()); + expected_sub_handles.emplace_back(readers.back()->get_native_reader().get_instance_handle()); + } + + // Wait for discovery + writer.wait_discovery(num_readers, std::chrono::seconds::zero()); + ASSERT_EQ(writer.get_matched(), num_readers); + + auto& native_writer = writer.get_native_writer(); + ReturnCode_t ret = native_writer.get_matched_subscriptions(sub_handles); + + // Check that the list of matched publication handles is correct + ASSERT_EQ(ret, eprosima::fastdds::dds::RETCODE_OK); + ASSERT_EQ(sub_handles.size(), num_readers); + ASSERT_TRUE(std::is_permutation(sub_handles.begin(), sub_handles.end(), expected_sub_handles.begin())); + + // Remove two readers and check that the list of matched publication handles is updated + readers.pop_back(); + readers.pop_back(); + expected_sub_handles.pop_back(); + expected_sub_handles.pop_back(); + + // Wait for undiscovery + writer.wait_reader_undiscovery(static_cast(num_readers - 2)); + + sub_handles.clear(); + ret = native_writer.get_matched_subscriptions(sub_handles); + ASSERT_EQ(ret, eprosima::fastdds::dds::RETCODE_OK); + ASSERT_EQ(sub_handles.size(), static_cast(num_readers - 2)); + ASSERT_TRUE(std::is_permutation(sub_handles.begin(), sub_handles.end(), expected_sub_handles.begin())); +} + +/** + * @test DDS-DW-API-GMS-03 + * + * The operation must provide the correct list of matched subscription handles in multiple + * participants scenario. Parameterize the test for different transports. + */ +TEST_P(DDSDataWriter, datawriter_get_matched_subscriptions_multiple_participants_correctly_behave) +{ + using InstanceHandle_t = eprosima::fastdds::rtps::InstanceHandle_t; + + PubSubParticipant part_1(1, 1, 1, 1); + PubSubParticipant part_2(1, 1, 1, 1); + + part_1.pub_topic_name(TEST_TOPIC_NAME); + part_1.sub_topic_name(TEST_TOPIC_NAME + "_1"); + part_2.pub_topic_name(TEST_TOPIC_NAME + "_1"); + part_2.sub_topic_name(TEST_TOPIC_NAME); + + ASSERT_TRUE(part_1.init_participant()); + ASSERT_TRUE(part_1.init_publisher(0)); + ASSERT_TRUE(part_1.init_subscriber(0)); + + ASSERT_TRUE(part_2.init_participant()); + ASSERT_TRUE(part_2.init_subscriber(0)); + ASSERT_TRUE(part_2.init_publisher(0)); + + part_1.pub_wait_discovery(); + part_1.sub_wait_discovery(); + + part_2.pub_wait_discovery(); + part_2.sub_wait_discovery(); + + auto& writer_p1 = part_1.get_native_writer(0); + auto& writer_p2 = part_2.get_native_writer(0); + + std::vector sub_handles_p1; + std::vector sub_handles_p2; + + ReturnCode_t ret = writer_p1.get_matched_subscriptions(sub_handles_p1); + ASSERT_EQ(ret, eprosima::fastdds::dds::RETCODE_OK); + ASSERT_EQ(sub_handles_p1.size(), 1u); + ASSERT_EQ(sub_handles_p1[0], part_2.get_native_reader(0).get_instance_handle()); + + ret = writer_p2.get_matched_subscriptions(sub_handles_p2); + ASSERT_EQ(ret, eprosima::fastdds::dds::RETCODE_OK); + ASSERT_EQ(sub_handles_p2.size(), 1u); + ASSERT_EQ(sub_handles_p2[0], part_1.get_native_reader(0).get_instance_handle()); +} + +// This tests checks that non-default QoS are correctly sent in the Data(w) +// Only half of the QoS are modified, so the other half should be the default ones and they should not +// be sent. The opposite scenario will be checked in the following test. +// QoS that should be sent: +// - DurabilityQosPolicy +// - DurabilityServiceQosPolicy +// - DeadlineQosPolicy +// - LatencyBudgetQosPolicy +// - LivelinessQosPolicy +// - ReliabilityQosPolicy +// - LifespanQosPolicy +// - UserDataQosPolicy +// - OwnershipQosPolicy +// - OwnershipStrengthQosPolicy +TEST_P(DDSDataWriter, datawriter_sends_non_default_qos_a) +{ + if (TRANSPORT != GetParam()) + { + GTEST_SKIP() << "Only makes sense on TRANSPORT"; + return; + } + + std::atomic qos_found { 0 }; + std::vector expected_qos_pids = { + eprosima::fastdds::dds::PID_DURABILITY, + eprosima::fastdds::dds::PID_DURABILITY_SERVICE, + eprosima::fastdds::dds::PID_DEADLINE, + eprosima::fastdds::dds::PID_LATENCY_BUDGET, + eprosima::fastdds::dds::PID_LIVELINESS, + eprosima::fastdds::dds::PID_RELIABILITY, + eprosima::fastdds::dds::PID_LIFESPAN, + eprosima::fastdds::dds::PID_USER_DATA, + eprosima::fastdds::dds::PID_OWNERSHIP, + eprosima::fastdds::dds::PID_OWNERSHIP_STRENGTH + }; + const uint8_t expected_qos_size = static_cast(expected_qos_pids.size()); + + PubSubWriter writer(TEST_TOPIC_NAME); + PubSubReader reader(TEST_TOPIC_NAME); + + auto test_transport = std::make_shared(); + test_transport->drop_builtin_data_messages_filter_ = [&](rtps::CDRMessage_t& msg) + { + return check_qos_in_data_w(msg, qos_found, expected_qos_pids); + }; + + eprosima::fastdds::dds::DataWriterQos dw_qos; + dw_qos.durability().kind = eprosima::fastdds::dds::VOLATILE_DURABILITY_QOS; + dw_qos.durability_service().history_depth = 7; + dw_qos.deadline().period = { 42, 0 }; + dw_qos.latency_budget().duration = { 42, 0 }; + dw_qos.liveliness().lease_duration = { 42, 0 }; + dw_qos.liveliness().announcement_period = { 39, 0 }; + dw_qos.reliability().kind = eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS; + dw_qos.reliability().max_blocking_time = { 42, 0 }; + dw_qos.lifespan().duration = { 42, 0 }; + std::vector vec; + eprosima::fastdds::rtps::octet val = 42; + vec.push_back(val); + dw_qos.user_data().data_vec(vec); + dw_qos.ownership().kind = eprosima::fastdds::dds::EXCLUSIVE_OWNERSHIP_QOS; + dw_qos.ownership_strength().value = 42; + dw_qos.data_sharing().off(); + + // Modify reader's QoS just to match the writer's + eprosima::fastdds::dds::DataReaderQos dr_qos; + dr_qos.ownership().kind = eprosima::fastdds::dds::EXCLUSIVE_OWNERSHIP_QOS; + dr_qos.data_sharing().off(); + + writer.disable_builtin_transport() + .add_user_transport_to_pparams(test_transport) + .data_writer_qos(dw_qos); + reader.data_reader_qos(dr_qos); + + writer.init(); + reader.init(); + ASSERT_TRUE(writer.isInitialized()); + ASSERT_TRUE(reader.isInitialized()); + + writer.wait_discovery(); + reader.wait_discovery(); + + EXPECT_EQ(qos_found.load(), expected_qos_size); + EXPECT_EQ(expected_qos_pids.size(), 0u); +} + +// This tests checks that non-default QoS are correctly sent in the Data(w) +// Only half of the QoS are modified, so the other half should be the default ones and they should not +// be sent. The opposite scenario will be checked in the previous test. +// QoS that should be sent: +// - DurabilityQosPolicy (always sent) +// - PresentationQosPolicy +// - PartitionQosPolicy +// - DisablePositiveACKsQosPolicy +// - DataSharingQosPolicy +// - GroupDataQosPolicy +// - DataRepresentationQosPolicy +// - HistoryQosPolicy +TEST_P(DDSDataWriter, datawriter_sends_non_default_qos_b) +{ + if (TRANSPORT != GetParam()) + { + GTEST_SKIP() << "Only makes sense on TRANSPORT"; + return; + } + + std::atomic qos_found { 0 }; + std::vector expected_qos_pids = { + eprosima::fastdds::dds::PID_DURABILITY, + eprosima::fastdds::dds::PID_PRESENTATION, + eprosima::fastdds::dds::PID_PARTITION, + eprosima::fastdds::dds::PID_DISABLE_POSITIVE_ACKS, + eprosima::fastdds::dds::PID_DATASHARING, + eprosima::fastdds::dds::PID_GROUP_DATA, + eprosima::fastdds::dds::PID_DATA_REPRESENTATION, + eprosima::fastdds::dds::PID_HISTORY + }; + const uint8_t expected_qos_size = static_cast(expected_qos_pids.size()); + + PubSubWriter writer(TEST_TOPIC_NAME); + PubSubReader reader(TEST_TOPIC_NAME); + + auto test_transport = std::make_shared(); + test_transport->drop_builtin_data_messages_filter_ = [&](rtps::CDRMessage_t& msg) + { + return check_qos_in_data_w(msg, qos_found, expected_qos_pids); + }; + + eprosima::fastdds::dds::PublisherQos pub_qos; + pub_qos.presentation().access_scope = eprosima::fastdds::dds::GROUP_PRESENTATION_QOS; + pub_qos.partition().push_back("partition_1"); + std::vector vec; + eprosima::fastdds::rtps::octet val = 42; + vec.push_back(val); + pub_qos.group_data().data_vec(vec); + eprosima::fastdds::dds::DataWriterQos dw_qos; + dw_qos.durability().kind = eprosima::fastdds::dds::TRANSIENT_LOCAL_DURABILITY_QOS; + dw_qos.reliability().kind = eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS; + dw_qos.reliable_writer_qos().disable_positive_acks.enabled = true; + dw_qos.reliable_writer_qos().disable_positive_acks.duration = { 42, 0 }; + dw_qos.representation().m_value.push_back(eprosima::fastdds::dds::DataRepresentationId_t::XCDR_DATA_REPRESENTATION); + dw_qos.history().kind = eprosima::fastdds::dds::KEEP_LAST_HISTORY_QOS; + dw_qos.history().depth = 42; + + // Modify reader's QoS just to match the writer's + eprosima::fastdds::dds::SubscriberQos sub_qos; + sub_qos.partition().push_back("partition_1"); + eprosima::fastdds::dds::DataReaderQos dr_qos; + +#ifdef _WIN32 + dw_qos.data_sharing().on("c:\\programdata\\eprosima\\fastdds_interprocess\\"); + dr_qos.data_sharing().on("c:\\programdata\\eprosima\\fastdds_interprocess\\"); +#elif __APPLE__ + dw_qos.data_sharing().on("/private/tmp/boost_interprocess/"); + dr_qos.data_sharing().on("/private/tmp/boost_interprocess/"); +#elif __linux__ + dw_qos.data_sharing().on("/dev/shm"); + dr_qos.data_sharing().on("/dev/shm"); +#else + throw std::runtime_error(std::string("Platform not supported")); +#endif // ifdef _WIN32 + + writer.disable_builtin_transport() + .add_user_transport_to_pparams(test_transport) + .publisher_qos(pub_qos) + .data_writer_qos(dw_qos); + reader.subscriber_qos(sub_qos) + .data_reader_qos(dr_qos); + + writer.init(); + reader.init(); + ASSERT_TRUE(writer.isInitialized()); + ASSERT_TRUE(reader.isInitialized()); + + writer.wait_discovery(); + reader.wait_discovery(); + + EXPECT_EQ(qos_found.load(), expected_qos_size); + EXPECT_EQ(expected_qos_pids.size(), 0u); +} + +// This tests checks that non-default optional QoS are correctly sent in the Data(w) +// QoS that should be sent: +// - ResourceLimitsQosPolicy +// - TransportPriorityQosPolicy +// - WriterDataLifecycleQosPolicy +// - PublishModeQosPolicy +// - RTPSReliableWriterQos +// - RTPSEndpointQos +// - WriterResourceLimitsQos +// a) The test is run with the property set to false, so the optional QoS are not serialized. +// b) The test is run with the property set to true, so the optional QoS are serialized. +// c) The test is run with the default QoS and the property set to true, so the optional QoS are not serialized. +TEST_P(DDSDataWriter, datawriter_sends_non_default_qos_optional) +{ + if (TRANSPORT != GetParam()) + { + GTEST_SKIP() << "Only makes sense on TRANSPORT"; + return; + } + + std::atomic qos_found { 0 }; + std::vector expected_qos_pids = { + eprosima::fastdds::dds::PID_RESOURCE_LIMITS, + eprosima::fastdds::dds::PID_TRANSPORT_PRIORITY, + eprosima::fastdds::dds::PID_WRITER_DATA_LIFECYCLE, + eprosima::fastdds::dds::PID_PUBLISH_MODE, + eprosima::fastdds::dds::PID_RTPS_RELIABLE_WRITER, + eprosima::fastdds::dds::PID_RTPS_ENDPOINT, + eprosima::fastdds::dds::PID_WRITER_RESOURCE_LIMITS, + }; + const uint8_t expected_qos_size = static_cast(expected_qos_pids.size()); + + PubSubWriter writer(TEST_TOPIC_NAME); + PubSubReader reader(TEST_TOPIC_NAME); + + auto test_transport = std::make_shared(); + test_transport->drop_builtin_data_messages_filter_ = [&](rtps::CDRMessage_t& msg) + { + return check_qos_in_data_w(msg, qos_found, expected_qos_pids); + }; + + eprosima::fastdds::dds::DataWriterQos dw_qos = eprosima::fastdds::dds::DATAWRITER_QOS_DEFAULT; + dw_qos.resource_limits().max_samples = 1000; + dw_qos.writer_data_lifecycle().autodispose_unregistered_instances = false; + dw_qos.transport_priority().value = 42; + dw_qos.reliable_writer_qos().times.initial_heartbeat_delay = { 4, 0 }; + dw_qos.endpoint().entity_id = 42; + dw_qos.publish_mode().kind = eprosima::fastdds::dds::ASYNCHRONOUS_PUBLISH_MODE; + dw_qos.writer_resource_limits().matched_subscriber_allocation.initial = 1; + dw_qos.data_sharing().off(); + + // Default writer's QoS + eprosima::fastdds::dds::DataReaderQos dr_qos = eprosima::fastdds::dds::DATAREADER_QOS_DEFAULT; + dr_qos.data_sharing().off(); + + writer.disable_builtin_transport() + .add_user_transport_to_pparams(test_transport) + .data_writer_qos(dw_qos); + reader.data_reader_qos(dr_qos); + + // a) Init both entities without setting the property + writer.init(); + reader.init(); + ASSERT_TRUE(writer.isInitialized()); + ASSERT_TRUE(reader.isInitialized()); + + writer.wait_discovery(); + reader.wait_discovery(); + + // No optional QoS should be sent. Only PID_DURABILITY as it is always sent + EXPECT_EQ(qos_found.load(), 1u); + EXPECT_EQ(expected_qos_pids.size(), expected_qos_size); + + // b) Now set the property to serialize optional QoS and re-init the writer + writer.destroy(); + reader.wait_writer_undiscovery(); + qos_found.store(0); + + eprosima::fastdds::dds::PropertyPolicyQos properties; + properties.properties().emplace_back("fastdds.serialize_optional_qos", "true"); + writer.property_policy(properties); + + writer.init(); + ASSERT_TRUE(writer.isInitialized()); + + writer.wait_discovery(); + reader.wait_discovery(); + + // Check that the optional QoS are serialized + PID_DURABILITY as it is always sent + EXPECT_EQ(qos_found.load(), expected_qos_size + 1); + EXPECT_EQ(expected_qos_pids.size(), 0u); + + // c) Now re-init the writer with default QoS and the property set + writer.destroy(); + reader.wait_writer_undiscovery(); + qos_found.store(0); + + dw_qos = eprosima::fastdds::dds::DATAWRITER_QOS_DEFAULT; + dw_qos.data_sharing().off(); + + writer.data_writer_qos(dw_qos) + .init(); + ASSERT_TRUE(writer.isInitialized()); + + writer.wait_discovery(); + reader.wait_discovery(); + + // Check that no optional QoS are serialized. Only PID_DURABILITY as it is always sent + EXPECT_EQ(qos_found.load(), 1u); +} + +/** + * @test DataWriter Sample prefilter feature + * + * This test checks that prefilter reset correctly works. + */ +TEST(DDSDataWriter, datawriter_prefilter_reset) +{ + struct CustomPreFilter : public eprosima::fastdds::dds::IContentFilter + { + ~CustomPreFilter() override = default; + + //! Custom filter for the HelloWorld example + bool evaluate( + const SerializedPayload&, + const FilterSampleInfo&, + const rtps::GUID_t& ) const override + { + /* sample should not be sent */ + return false; + } + + }; + + PubSubWriter writer(TEST_TOPIC_NAME); + PubSubReader reader(TEST_TOPIC_NAME); + + // Initialize writer + writer.init(); + ASSERT_TRUE(writer.isInitialized()); + + // Set a prefilter on the writer + // discarding all samples + ASSERT_EQ(writer.set_sample_prefilter( + std::make_shared()), + eprosima::fastdds::dds::RETCODE_OK); + + // Initialize reader + reader.init(); + ASSERT_TRUE(reader.isInitialized()); + + // Wait for discovery + writer.wait_discovery(); + reader.wait_discovery(); + + // The reader should not receive any sample + auto data = default_helloworld_data_generator(); + + reader.startReception(data); + writer.send(data); + + // Wait for the reader to timeout receiving the samples + ASSERT_EQ(reader.block_for_all(std::chrono::seconds(1)), 0u); + + // Reset the prefilter + ASSERT_EQ(writer.set_sample_prefilter( + nullptr), + eprosima::fastdds::dds::RETCODE_OK); + + data = default_helloworld_data_generator(); + + // The reader should now receive the samples + writer.send(data); + ASSERT_EQ(reader.block_for_all(std::chrono::seconds(3)), 10u); +} + +/** + * @test DataWriter Sample prefilter feature + * + * This test checks that prefilter works correctly with standard filtering disabled. + */ +TEST(DDSDataWriter, datawriter_prefilter_no_standard_filtering) +{ + struct CustomPreFilter : public eprosima::fastdds::dds::IContentFilter + { + ~CustomPreFilter() override = default; + + //! Custom filter for the HelloWorld example + bool evaluate( + const SerializedPayload&, + const FilterSampleInfo&, + const rtps::GUID_t& ) const override + { + /* sample should not be sent */ + return false; + } + + }; + + PubSubWriter writer(TEST_TOPIC_NAME); + PubSubReader reader(TEST_TOPIC_NAME); + + // Initialize writer + // with no standard filtering enabled (set liveliness lease duration + // to something other than infinite) + writer.liveliness_lease_duration({2, 0}) + .liveliness_announcement_period({1, 0 }) + .init(); + ASSERT_TRUE(writer.isInitialized()); + + // Set a prefilter on the writer + // discarding all samples + ASSERT_EQ(writer.set_sample_prefilter( + std::make_shared()), + eprosima::fastdds::dds::RETCODE_OK); + + // Initialize reader + reader.init(); + ASSERT_TRUE(reader.isInitialized()); + + // Wait for discovery + writer.wait_discovery(); + reader.wait_discovery(); + + // The reader should not receive any sample + // As the prefilter is set to discard all samples + auto data = default_helloworld_data_generator(); + + reader.startReception(data); + writer.send(data); + + // Wait for the reader to timeout receiving the samples + ASSERT_EQ(reader.block_for_all(std::chrono::seconds(1)), 0u); +} + +/** + * @test DataWriter Sample prefilter feature + * + * This test asserts that prefiltering by write_params on a DataWriter + * correctly works. The test sets a prefilter to only send samples + * to the second reader discovered. + */ +TEST_P(DDSDataWriter, datawriter_prefilter_filtering_by_write_params) +{ + struct CustomUserWriteData : public rtps::WriteParams::UserWriteData + { + CustomUserWriteData( + const rtps::GuidPrefix_t& guid_prefix) + : filtered_out_prefix_(guid_prefix) + { + } + + rtps::GuidPrefix_t filtered_out_prefix_; + }; + + struct CustomPreFilter : public eprosima::fastdds::dds::IContentFilter + { + ~CustomPreFilter() override = default; + + //! Custom filter for the HelloWorld example + bool evaluate( + const SerializedPayload&, + const FilterSampleInfo& filter_sample_info, + const rtps::GUID_t& reader_guid) const override + { + bool sample_should_be_sent = true; + + auto custom_write_data = + std::static_pointer_cast(filter_sample_info.user_write_data); + + // If the reader is the one we want to filter out, do not send the sample + if (custom_write_data->filtered_out_prefix_ == reader_guid.guidPrefix) + { + sample_should_be_sent = false; + } + return sample_should_be_sent; + } + + }; + + PubSubWriter writer(TEST_TOPIC_NAME); + PubSubReader filtered_reader(TEST_TOPIC_NAME); + PubSubReader receiving_reader(TEST_TOPIC_NAME); + + // Initialize writer and the filtered reader + writer.init(); + ASSERT_TRUE(writer.isInitialized()); + filtered_reader.init(); + ASSERT_TRUE(filtered_reader.isInitialized()); + + // wait for discovery between writer and filtered reader + writer.wait_discovery(); + filtered_reader.wait_discovery(); + + // Set a prefilter on the filtered reader + ASSERT_EQ(writer.set_sample_prefilter( + std::make_shared()), + eprosima::fastdds::dds::RETCODE_OK); + + // Create a second reader that will not be filtered + receiving_reader.init(); + ASSERT_TRUE(receiving_reader.isInitialized()); + + // Wait for discovery between the receiving reader and the writer + writer.wait_discovery(2); + receiving_reader.wait_discovery(); + + // Set a user write data on the writer to filter out the filtered reader + rtps::WriteParams write_params; + write_params.user_write_data(std::make_shared( + filtered_reader.datareader_guid().guidPrefix)); + + auto data = default_helloworld_data_generator(); + + filtered_reader.startReception(data); + receiving_reader.startReception(data); + + writer.send(data, 50, &write_params); + // Wait for the filtered reader to timeout receiving the sample + ASSERT_EQ(filtered_reader.block_for_all(std::chrono::seconds(1)), 0u); + // The receiving reader should have received the samples + ASSERT_EQ(receiving_reader.block_for_all(std::chrono::seconds(1)), 10u); +} + +/** + * @test DataWriter Sample prefilter feature + * + * This test asserts that prefiltering by payload on a DataWriter + * correctly works. The test sets a prefilter to only send samples + * with index >= 5 to the discovered reader. + */ +TEST_P(DDSDataWriter, datawriter_prefilter_filtering_by_payload) +{ + // TODO(Mario-DL): Remove when multiple filtering readers case is fixed for data-sharing + if (enable_datasharing) + { + GTEST_SKIP() << "Several filtering readers not correctly working on data sharing"; + } + + struct CustomUserWriteData : public rtps::WriteParams::UserWriteData + { + CustomUserWriteData( + const uint16_t& desired_idx) + : desired_idx_(desired_idx) + { + } + + uint16_t desired_idx_; + }; + + struct CustomPreFilter : public eprosima::fastdds::dds::IContentFilter + { + ~CustomPreFilter() override = default; + + //! Custom filter for the HelloWorld example + bool evaluate( + const SerializedPayload& payload, + const FilterSampleInfo& filter_sample_info, + const rtps::GUID_t&) const override + { + HelloWorldPubSubType hello_world_type_support; + HelloWorld hello_world_sample; + + hello_world_type_support.deserialize(*const_cast(&payload), &hello_world_sample); + + bool sample_should_be_sent = true; + + auto custom_write_data = + std::static_pointer_cast(filter_sample_info.user_write_data); + + // Filter out samples <= desired_index + if (hello_world_sample.index() <= custom_write_data->desired_idx_) + { + sample_should_be_sent = false; + } + return sample_should_be_sent; + } + + }; + + PubSubWriter writer(TEST_TOPIC_NAME); + PubSubReader reader(TEST_TOPIC_NAME); + + // Initialize writer and the filtered reader + writer.init(); + ASSERT_TRUE(writer.isInitialized()); + reader.init(); + ASSERT_TRUE(reader.isInitialized()); + + // wait for discovery between writer and filtered reader + writer.wait_discovery(); + reader.wait_discovery(); + + // Set a prefilter on the filtered reader + ASSERT_EQ(writer.set_sample_prefilter( + std::make_shared()), + eprosima::fastdds::dds::RETCODE_OK); + + // Set a user write data on the writer to filter out samples with idx <= 5 + rtps::WriteParams write_params; + write_params.user_write_data(std::make_shared( + (uint16_t)5u)); + + auto data = default_helloworld_data_generator(); + + reader.startReception(data); + + writer.send(data, 50, &write_params); + // Reader should have received the samples + ASSERT_EQ(reader.block_for_all(std::chrono::seconds(1)), 5u); +} + +/** + * @brief Helper class to capture the transport priorities seen by a custom transport. + */ +class TransportPriorityCaptures +{ +public: + + /** + * @brief Constructor that initializes the destination locator and expected priorities. + * + * @param ip_address The IP address of the destination locator. + * @param port The port of the destination locator. + */ + TransportPriorityCaptures( + const std::string& ip_address, + uint32_t port) + { + IPLocator::createLocator(LOCATOR_KIND_UDPv4, ip_address, port, destination_locator_); + expected_priorities_set_.insert(0); + } + + /** + * @brief Method called for each destination locator seen by the transport. + * + * @param locator The locator that was seen. + * @param transport_priority The transport priority being used in the send operation. + */ + void seen_locator_and_priority( + const rtps::Locator& locator, + int32_t transport_priority) + { + std::lock_guard lock(mtx_); + if (locator == destination_locator_) + { + // Checks that when sending to the specified destination locator the transport priority is the expected one. + EXPECT_EQ(transport_priority, expected_transport_priority_); + } + else + { + // Checks that when sending to other locators the transport priority is `0`. + EXPECT_EQ(transport_priority, 0); + } + // Add the transport priority to `seen_priorities_set`. + seen_priorities_set_.insert(transport_priority); + } + + /** + * @brief Sets the expected transport priority for the following send operations. + * + * @param priority The expected transport priority. + */ + void set_expected_transport_priority( + int32_t priority) + { + std::lock_guard lock(mtx_); + expected_transport_priority_ = priority; + expected_priorities_set_.insert(priority); + } + + /** + * @brief Checks that the seen priorities match the expected priorities. + * + * This method is used to assert that the transport priorities seen during the test match the expected ones. + */ + void check_seen_priorities() const + { + std::lock_guard lock(mtx_); + EXPECT_EQ(seen_priorities_set_, expected_priorities_set_); + } + +private: + + mutable std::mutex mtx_; + int32_t expected_transport_priority_ = 0; + std::set seen_priorities_set_; + std::set expected_priorities_set_; + rtps::Locator destination_locator_; +}; + +/** + * @test TRP-FUN-01 Check that the values set in the `TRANSPORT_PRIORITY` QoS of a DataWriter + * are passed to the transport layer. + */ +TEST(DDSDataWriter, transport_priority) +{ + namespace fdds = eprosima::fastdds::dds; + + // ----------- Configuration ----------- + constexpr int32_t PRIORITY_1 = 12; + constexpr int32_t PRIORITY_2 = -23; + const std::string ip_address = "127.0.0.1"; + constexpr uint16_t port = 6999; + TransportPriorityCaptures captures(ip_address, port); + + // ----------- Setup ----------- + + // 1. Set `expected_transport_priority` to `PRIORITY_1`. + captures.set_expected_transport_priority(PRIORITY_1); + + // 2. Prepare a custom transport for which the `transport_priority` lambda is set to a function that: + auto test_transport = std::make_shared(); + // a. Captures a `destination_locator`, a `expected_transport_priority`, and a `seen_priorities_set`. + test_transport->locator_filter_ = [&captures]( + const rtps::Locator& locator, int32_t transport_priority) + { + // b. Checks that when sending to the captured destination locator the transport priority is the expected one. + // c. Checks that when sending to other locators the transport priority is `0`. + // d. Adds the transport priority to `seen_priorities_set`. + captures.seen_locator_and_priority(locator, transport_priority); + // Never drop the destination locator + return false; + }; + + // 3. Prepare a `PubSubReader`: + PubSubReader reader(TEST_TOPIC_NAME); + reader.history_depth(10) + // a. Using the custom transport described above. + .disable_builtin_transport() + .add_user_transport_to_pparams(test_transport) + // b. Using `destination_locator` as the DataReader reception locator. + .add_to_unicast_locator_list(ip_address, port); + + // 4. Prepare a `PubSubWriter`: + PubSubWriter writer(TEST_TOPIC_NAME); + writer.history_depth(10) + // a. Using the custom transport described above. + .disable_builtin_transport() + .add_user_transport_to_pparams(test_transport) + // b. Configured with a `TRANSPORT_PRIORITY` of `PRIORITY_1`. + .transport_priority(PRIORITY_1); + + // ----------- Procedure ----------- + + // 1. Initialize both the `PubSubReader` and the `PubSubWriter`. + reader.init(); + ASSERT_TRUE(reader.isInitialized()); + writer.init(); + ASSERT_TRUE(writer.isInitialized()); + + // 2. Wait for them to match. + writer.wait_discovery(); + reader.wait_discovery(); + + // 3. Write some samples using the `PubSubWriter`. + auto data = default_helloworld_data_generator(); + reader.startReception(data); + writer.send(data); + ASSERT_TRUE(data.empty()); + + // 4. Block till the `PubSubReader` receives all samples. + reader.block_for_all(); + + // 5. Change the value of `expected_transport_priority` to `PRIORITY_2` and update the `TRANSPORT_PRIORITY` of the `PubSubWriter` accordingly. + captures.set_expected_transport_priority(PRIORITY_2); + writer.transport_priority(PRIORITY_2); + EXPECT_TRUE(writer.set_qos()); + + // 6. Write some samples using the `PubSubWriter`. + data = default_helloworld_data_generator(); + reader.startReception(data); + writer.send(data); + ASSERT_TRUE(data.empty()); + + // 7. Block till the `PubSubReader` receives all samples. + reader.block_for_all(); + + // ----------- Assertions ----------- + + // 1. Every datagram sent to `destination_locator` uses a transport priority of `expected_transport_priority`. + // Automatically checked inside the locator filter lambda. + + // 2. Number of entries in `seen_priorities_set` is `3`, and it contains values `0`, `PRIORITY_1`, and `PRIORITY_2`. + captures.check_seen_priorities(); +} + +/** + * @test TRP-FUN-02 Check that retransmission of samples use updated values of the transport priority + * (i.e. that priority is not associated to the sample, but to the `DataWriter`) + */ +TEST(DDSDataWriter, transport_priority_late_joiner) +{ + namespace fdds = eprosima::fastdds::dds; + + // ----------- Configuration ----------- + constexpr int32_t PRIORITY_1 = 42; + const std::string ip_address = "127.0.0.1"; + constexpr uint16_t port = 6998; + TransportPriorityCaptures captures(ip_address, port); + + // ----------- Setup ----------- + + // 1. Set `expected_transport_priority` to `PRIORITY_1`. + captures.set_expected_transport_priority(PRIORITY_1); + + // 2. Prepare a custom transport for which the `transport_priority` lambda is set to a function that: + auto test_transport = std::make_shared(); + // a. Captures a `destination_locator`, a `expected_transport_priority`, and a `seen_priorities_set`. + test_transport->locator_filter_ = [&captures]( + const rtps::Locator& locator, int32_t transport_priority) + { + // b. Checks that when sending to the captured destination locator the transport priority is the expected one. + // c. Checks that when sending to other locators the transport priority is `0`. + // d. Adds the transport priority to `seen_priorities_set`. + captures.seen_locator_and_priority(locator, transport_priority); + // Never drop the destination locator + return false; + }; + + // 3. Prepare a `PubSubReader`: + PubSubReader reader(TEST_TOPIC_NAME); + reader.history_depth(10) + // a. Using `destination_locator` as the DataReader reception locator. + .add_to_unicast_locator_list(ip_address, port) + // b. Using `TRANSIENT_LOCAL` durability, and `RELIABLE` reliability. + .durability_kind(fdds::DurabilityQosPolicyKind::TRANSIENT_LOCAL_DURABILITY_QOS) + .reliability(fdds::ReliabilityQosPolicyKind::RELIABLE_RELIABILITY_QOS); + + // 4. Prepare a `PubSubWriter`: + PubSubWriter writer(TEST_TOPIC_NAME); + writer.history_depth(10) + // a. Using the custom transport described above. + .disable_builtin_transport() + .add_user_transport_to_pparams(test_transport) + // b. Configured with a `TRANSPORT_PRIORITY` of `0`. + .transport_priority(0) + // c. Using `TRANSIENT_LOCAL` durability, and `RELIABLE` reliability. + .durability_kind(fdds::DurabilityQosPolicyKind::TRANSIENT_LOCAL_DURABILITY_QOS) + .reliability(fdds::ReliabilityQosPolicyKind::RELIABLE_RELIABILITY_QOS); + + // ----------- Procedure ----------- + + // 1. Initialize the `PubSubWriter`. + writer.init(); + ASSERT_TRUE(writer.isInitialized()); + + // 2. Write some samples using the `PubSubWriter`. + auto original_data = default_helloworld_data_generator(); + auto data = original_data; + writer.send(data); + ASSERT_TRUE(data.empty()); + + // 3. Update the `TRANSPORT_PRIORITY` of the `PubSubWriter` to `expected_transport_priority`. + writer.transport_priority(PRIORITY_1); + EXPECT_TRUE(writer.set_qos()); + + // 4. Initialize the `PubSubReader`. + reader.init(); + ASSERT_TRUE(reader.isInitialized()); + + // 5. Block till the `PubSubReader` receives all samples. + reader.startReception(original_data); + reader.block_for_all(); + + // ----------- Assertions ----------- + + // 1. Every datagram sent to `destination_locator` uses a transport priority of `expected_transport_priority`. + // Automatically checked inside the locator filter lambda. + + // 2. Number of entries in `seen_priorities_set` is `2`, and it contains values `0` and `PRIORITY_1`. + captures.check_seen_priorities(); +} + +/** + * @test TRP-FUN-03 Check that TRANSPORT_PRIORITY QoS is mutable. + */ +TEST(DDSDataWriter, transport_priority_mutable) +{ + namespace fdds = eprosima::fastdds::dds; + + // ----------- Configuration ----------- + constexpr int32_t PRIORITY_2 = -23; + constexpr int32_t PRIORITY_3 = 42; + + // ----------- Setup ----------- + + PubSubWriter writer(TEST_TOPIC_NAME); + // We want to check that the transport priority is correctly transmitted, + // so we need to serialize the optional QoS in the DataWriter discovery. + rtps::PropertyPolicy properties; + properties.properties().emplace_back("fastdds.serialize_optional_qos", "true"); + writer.property_policy(properties); + + // ----------- Procedure ----------- + + // 1. Create a participant with a custom listener that captures the transport priority + // of the discovered writer. + struct CustomListener : public fdds::DomainParticipantListener + { + void on_data_writer_discovery( + fdds::DomainParticipant* /* participant */, + rtps::WriterDiscoveryStatus reason, + const fdds::PublicationBuiltinTopicData& info, + bool& should_be_ignored) override + { + std::lock_guard lock(mtx_); + + if (reason == rtps::WriterDiscoveryStatus::DISCOVERED_WRITER) + { + // Capture the transport priority of the discovered writer. + EXPECT_TRUE(info.transport_priority.has_value()); + if (info.transport_priority.has_value()) + { + discovered_transport_priority_ = info.transport_priority->value; + } + } + else if (reason == rtps::WriterDiscoveryStatus::CHANGED_QOS_WRITER) + { + // Capture the updated transport priority of the writer. + EXPECT_TRUE(info.transport_priority.has_value()); + if (info.transport_priority.has_value()) + { + updated_transport_priority_ = info.transport_priority->value; + } + } + cv_.notify_all(); + + should_be_ignored = false; + } + + void wait_discovery() + { + std::unique_lock lock(mtx_); + // Wait for the transport priority to be discovered. + cv_.wait(lock, [this] + { + return discovered_transport_priority_ != 0; + }); + } + + void wait_update() + { + std::unique_lock lock(mtx_); + // Wait for the transport priority to be updated. + cv_.wait(lock, [this] + { + return updated_transport_priority_ != 0; + }); + } + + void check_expectations() const + { + std::lock_guard lock(mtx_); + int32_t prio = PRIORITY_2; + EXPECT_EQ(discovered_transport_priority_, prio); + prio = PRIORITY_3; + EXPECT_EQ(updated_transport_priority_, prio); + } + + private: + + mutable std::mutex mtx_; + std::condition_variable cv_; + int32_t discovered_transport_priority_ = 0; + int32_t updated_transport_priority_ = 0; + }; + + CustomListener custom_listener; + auto factory = fdds::DomainParticipantFactory::get_shared_instance(); + fdds::DomainId_t domain_id = static_cast(GET_PID()) % 230; + fdds::DomainParticipant* participant = factory->create_participant( + domain_id, fdds::PARTICIPANT_QOS_DEFAULT, &custom_listener, fdds::StatusMask::none()); + + // 2. Create a disabled `DataWriter` with default QoS. + fdds::PublisherQos pub_qos{}; + pub_qos.entity_factory().autoenable_created_entities = false; + writer.publisher_qos(pub_qos).init(); + fdds::DataWriter& data_writer = writer.get_native_writer(); + + // 3. Get the writer's current QoS into `current_qos` and save the transport priority into `transport_priority_1`. + auto current_qos = data_writer.get_qos(); + int32_t transport_priority_1 = current_qos.transport_priority().value; + + // 4. Update the transport priority in `current_qos` to `PRIORITY_2` and set the `DataWriter` QoS to `current_qos`. + current_qos.transport_priority().value = PRIORITY_2; + EXPECT_EQ(data_writer.set_qos(current_qos), fdds::RETCODE_OK); + + // 5. Get the writer's current QoS into `current_qos` and save the transport priority into `transport_priority_2`. + current_qos = data_writer.get_qos(); + int32_t transport_priority_2 = current_qos.transport_priority().value; + + // 6. Enable the `DataWriter`. + EXPECT_EQ(data_writer.enable(), fdds::RETCODE_OK); + custom_listener.wait_discovery(); + + // 7. Update the transport priority in `current_qos` to `PRIORITY_3` and set the `DataWriter` QoS to `current_qos`. + current_qos.transport_priority().value = PRIORITY_3; + EXPECT_EQ(data_writer.set_qos(current_qos), fdds::RETCODE_OK); + + // 8. Get the writer's current QoS into `current_qos` and save the transport priority into `transport_priority_3`. + current_qos = data_writer.get_qos(); + int32_t transport_priority_3 = current_qos.transport_priority().value; + + // 9. Wait for the participant to discover the `DataWriter` and update the transport priority. + custom_listener.wait_update(); + + // ----------- Assertions ----------- + + // 1. `transport_priority_1` equals `0`. + EXPECT_EQ(transport_priority_1, 0); + + // 2. `transport_priority_2` equals `PRIORITY_2`. + EXPECT_EQ(transport_priority_2, PRIORITY_2); + + // 3. `transport_priority_3` equals `PRIORITY_3`. + EXPECT_EQ(transport_priority_3, PRIORITY_3); + + // 4. The other participant discovered and updated transport priorities + // are equal to `PRIORITY_2` and `PRIORITY_3`, respectively. + custom_listener.check_expectations(); + + // ----------- Cleanup ----------- + + factory->delete_participant(participant); +} + +/** + * @test TRP-FUN-04 Check that TRANSPORT_PRIORITY QoS can be loaded from an XML. + */ +TEST(DDSDataWriter, transport_priority_xml) +{ + namespace fdds = eprosima::fastdds::dds; + + // ----------- Configuration ----------- + constexpr int32_t PRIORITY_1 = 12; + + // ----------- Setup ----------- + + PubSubWriter writer(TEST_TOPIC_NAME); + + // ----------- Procedure ----------- + + // 1. Create a `DataWriter` with `create_datawriter_from_profile` using profile `TRP-FUN-04`. + writer.set_xml_filename("transport_priority_profile.xml"); + writer.set_datawriter_profile("TRP-FUN-04"); + writer.init(); + ASSERT_TRUE(writer.isInitialized()); + fdds::DataWriter& data_writer = writer.get_native_writer(); + + // 2. Get the writer's current QoS into `current_qos` and save the transport priority into `transport_priority_1`. + auto current_qos = data_writer.get_qos(); + int32_t transport_priority_1 = current_qos.transport_priority().value; + + // ----------- Assertions ----------- + + // 1. `transport_priority_1` equals `PRIORITY_1`. + EXPECT_EQ(transport_priority_1, PRIORITY_1); +} + +/** + * @test End-to-end test for the type support context feature. + * + * Verifies that: + * 1. set_type_support_context can be called before enable on both DataWriter and DataReader. + * 2. The context is passed through to the context-aware serialize() and deserialize() overloads + * when data actually flows between writer and reader over transport. + */ +TEST(DDSDataWriter, type_support_context_end_to_end) +{ + using namespace eprosima::fastdds::dds; + using namespace eprosima::fastdds::rtps; + + // Tracks calls to context-aware type support methods. + struct CallCountingContext : public TopicDataType::Context + { + int serialize_calls{0}; + int deserialize_calls{0}; + }; + + // Type support that intercepts the context-aware overloads used during data flow. + class ContextTrackingTypeSupport : public HelloWorldPubSubType + { + public: + + using HelloWorldPubSubType::HelloWorldPubSubType; + + bool serialize_ctx( + const std::shared_ptr& ctx, + const void* const data, + SerializedPayload_t& payload, + DataRepresentationId_t data_representation) override + { + if (auto tracking = std::dynamic_pointer_cast(ctx)) + { + tracking->serialize_calls += 1; + } + return HelloWorldPubSubType::serialize(data, payload, data_representation); + } + + bool deserialize_ctx( + const std::shared_ptr& ctx, + eprosima::fastdds::rtps::SerializedPayload_t& payload, + void* data) override + { + if (auto tracking = std::dynamic_pointer_cast(ctx)) + { + tracking->deserialize_calls += 1; + } + return HelloWorldPubSubType::deserialize(payload, data); + } + + }; + + DomainParticipant* participant = + DomainParticipantFactory::get_instance()->create_participant(0, PARTICIPANT_QOS_DEFAULT); + ASSERT_NE(participant, nullptr); + + TypeSupport ts(new ContextTrackingTypeSupport()); + ts.register_type(participant); + Topic* topic = participant->create_topic( + "type_support_context_topic", ts.get_type_name(), TOPIC_QOS_DEFAULT); + + // Create publisher/subscriber with autoenable off so we can set context before enabling + PublisherQos pub_qos = PUBLISHER_QOS_DEFAULT; + pub_qos.entity_factory().autoenable_created_entities = false; + Publisher* publisher = participant->create_publisher(pub_qos); + ASSERT_NE(publisher, nullptr); + + SubscriberQos sub_qos = SUBSCRIBER_QOS_DEFAULT; + sub_qos.entity_factory().autoenable_created_entities = false; + Subscriber* subscriber = participant->create_subscriber(sub_qos); + ASSERT_NE(subscriber, nullptr); + + // Create disabled reliable writer and reader. + DataWriterQos writer_qos = DATAWRITER_QOS_DEFAULT; + writer_qos.reliability().kind = RELIABLE_RELIABILITY_QOS; + writer_qos.durability().kind = TRANSIENT_LOCAL_DURABILITY_QOS; + DataWriter* datawriter = publisher->create_datawriter(topic, writer_qos); + ASSERT_NE(datawriter, nullptr); + EXPECT_FALSE(datawriter->is_enabled()); + + DataReaderQos reader_qos = DATAREADER_QOS_DEFAULT; + reader_qos.reliability().kind = RELIABLE_RELIABILITY_QOS; + reader_qos.durability().kind = TRANSIENT_LOCAL_DURABILITY_QOS; + DataReader* datareader = subscriber->create_datareader(topic, reader_qos); + ASSERT_NE(datareader, nullptr); + EXPECT_FALSE(datareader->is_enabled()); + + // Set tracking contexts on the disabled entities + auto writer_ctx = std::make_shared(); + auto reader_ctx = std::make_shared(); + ASSERT_EQ(RETCODE_OK, datawriter->set_type_support_context(writer_ctx)); + ASSERT_EQ(RETCODE_OK, datareader->set_type_support_context(reader_ctx)); + + // Enable both entities now that the contexts are set + ASSERT_EQ(RETCODE_OK, datawriter->enable()); + ASSERT_EQ(RETCODE_OK, datareader->enable()); + + // Write one sample and verify it is received + HelloWorld data; + data.index(1); + data.message("context test"); + ASSERT_EQ(RETCODE_OK, datawriter->write(&data)); + ASSERT_TRUE(datareader->wait_for_unread_message({10, 0})); + LoanableSequence datas; + SampleInfoSeq infos; + EXPECT_EQ(eprosima::fastdds::dds::RETCODE_OK, datareader->take(datas, infos)); + EXPECT_EQ(eprosima::fastdds::dds::RETCODE_OK, datareader->return_loan(datas, infos)); + + // Verify that the context-aware overloads were called + EXPECT_GT(writer_ctx->serialize_calls, 0); + EXPECT_EQ(writer_ctx->deserialize_calls, 0); + EXPECT_EQ(reader_ctx->serialize_calls, 0); + EXPECT_GT(reader_ctx->deserialize_calls, 0); + + EXPECT_EQ(RETCODE_OK, publisher->delete_datawriter(datawriter)); + EXPECT_EQ(RETCODE_OK, subscriber->delete_datareader(datareader)); + EXPECT_EQ(RETCODE_OK, participant->delete_topic(topic)); + EXPECT_EQ(RETCODE_OK, participant->delete_publisher(publisher)); + EXPECT_EQ(RETCODE_OK, participant->delete_subscriber(subscriber)); + EXPECT_EQ(RETCODE_OK, DomainParticipantFactory::get_instance()->delete_participant(participant)); +} + +>>>>>>> 25a43a7c3 (Add UBSan workflow and solve its errors (#6386)) #ifdef INSTANTIATE_TEST_SUITE_P #define GTEST_INSTANTIATE_TEST_MACRO(x, y, z, w) INSTANTIATE_TEST_SUITE_P(x, y, z, w) #else diff --git a/test/blackbox/common/DDSBlackboxTestsListeners.cpp b/test/blackbox/common/DDSBlackboxTestsListeners.cpp index f4c981880dc..1279508410f 100644 --- a/test/blackbox/common/DDSBlackboxTestsListeners.cpp +++ b/test/blackbox/common/DDSBlackboxTestsListeners.cpp @@ -39,12 +39,14 @@ using test_UDPv4TransportDescriptor = eprosima::fastdds::rtps::test_UDPv4Transpo std::string("incompatible_") + TEST_TOPIC_NAME) +namespace { enum communication_type { TRANSPORT, INTRAPROCESS, DATASHARING }; +} // namespace class DDSStatus : public testing::TestWithParam { diff --git a/test/blackbox/common/DDSBlackboxTestsMonitorService.cpp b/test/blackbox/common/DDSBlackboxTestsMonitorService.cpp new file mode 100644 index 00000000000..3abc191840f --- /dev/null +++ b/test/blackbox/common/DDSBlackboxTestsMonitorService.cpp @@ -0,0 +1,3029 @@ +// Copyright 2023 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// TODO(jlbueno): remove private header +#include +#include "../types/statistics/monitorservice_typesPubSubTypes.hpp" +#include "BlackboxTests.hpp" +#include "PubSubReader.hpp" +#include "PubSubWriter.hpp" + +using namespace eprosima::fastdds; +using namespace eprosima::fastdds::dds; +using namespace eprosima::fastdds::rtps; + +namespace { +enum communication_type +{ + TRANSPORT, + INTRAPROCESS, + DATASHARING +}; +} // namespace + +using MonitorServiceType = eprosima::fastdds::statistics::MonitorServiceStatusDataPubSubType; +using GUIDList = std::vector; +using StatisticsGUIDList = std::vector; + +struct SampleValidator; + +#ifdef FASTDDS_STATISTICS + +void validator_selector( + statistics::dds::DomainParticipant* participant, + SampleValidator*& validator, + const statistics::StatusKind::StatusKind status_kind, + SampleInfo& info, + MonitorServiceType::type& data, + std::list& total_msgs, + std::atomic& processed_count, + std::condition_variable& cv); + +class MonitorServiceParticipant +{ + +public: + + enum CallbackIndex + { + OFFERED_DEADLINE_MISSED_IDX, + OFFERED_INCOMPATIBLE_QOS_IDX, + LIVELINESS_LOST_IDX, + PUBLICATION_MATCHED_IDX, + REQUESTED_DEADLINE_MISSED_IDX, + REQUESTED_INCOMPATIBLE_QOS_IDX, + LIVELINESS_CHANGED_IDX, + SUBSCRIPTION_MATCHED_IDX, + SAMPLE_LOST_IDX, + MAX_SIZE + }; + + MonitorServiceParticipant() + : type_support_(new HelloWorldPubSubType()) + { + + } + + ~MonitorServiceParticipant() + { + auto dpf = DomainParticipantFactory::get_instance(); + if (nullptr != statistics_part_) + { + statistics_part_->delete_contained_entities(); + dpf->delete_participant(statistics_part_); + } + } + + void setup( + const DomainParticipantQos& qos) + { + auto participant = DomainParticipantFactory::get_instance()-> + create_participant((uint32_t)GET_PID() % 230, qos); + + setup(participant); + } + + void setup( + std::string profiles_file, + std::string profile, + uint32_t domain_id) + { + //! Load XML profiles + eprosima::fastdds::dds::DomainParticipantFactory::get_instance()->load_XML_profiles_file(profiles_file); + + eprosima::fastdds::dds::DomainParticipant* participant = + eprosima::fastdds::dds::DomainParticipantFactory::get_instance()-> + create_participant_with_profile(domain_id, profile); + + setup(participant); + } + + void setup() + { + DomainParticipantQos pqos; + pqos.wire_protocol().builtin.discovery_config.leaseDuration_announcementperiod = { 0, 250000000 }; + pqos.name() = "Monitor_Service_Participant"; + auto participant = DomainParticipantFactory::get_instance()-> + create_participant((uint32_t)GET_PID() % 230, pqos); + + setup(participant); + } + + void setup( + DomainParticipant* participant) + { + statistics_part_ = statistics::dds::DomainParticipant::narrow(participant); + ASSERT_NE(statistics_part_, nullptr); + + type_support_.register_type(participant); + + publisher_ = statistics_part_->create_publisher(PUBLISHER_QOS_DEFAULT); + ASSERT_NE(publisher_, nullptr); + + subscriber_ = statistics_part_->create_subscriber(SUBSCRIBER_QOS_DEFAULT); + ASSERT_NE(subscriber_, nullptr); + + create_topic(); + } + + void assert_liveliness() + { + for (auto& writer : writers_) + { + writer->assert_liveliness(); + } + } + + const StatisticsGUIDList& get_writer_guids() + { + return writer_stat_guids_; + } + + const StatisticsGUIDList& get_reader_guids() + { + return reader_stat_guids_; + } + + statistics::detail::GUID_s get_participant_guid() + { + return statistics::to_statistics_type(statistics_part_->guid()); + } + + ReturnCode_t enable_monitor_service() + { + return statistics_part_->enable_monitor_service(); + } + + ReturnCode_t disable_monitor_service() + { + return statistics_part_->disable_monitor_service(); + } + + const uint32_t& get_cb_count( + CallbackIndex cb_idx) + { + return listener_.get_cb_count_of(cb_idx); + } + + void create_topic( + int topic_number = 0) + { + topics_.emplace_back(statistics_part_->create_topic("test_" + std::to_string(topic_number), + type_support_.get_type_name(), TOPIC_QOS_DEFAULT)); + ASSERT_NE(topics_.back(), nullptr); + } + + void create_and_add_writer( + const DataWriterQos& qos = DATAWRITER_QOS_DEFAULT) + { + writers_.emplace_back(publisher_->create_datawriter(topics_.back(), qos, &listener_)); + ASSERT_NE(writers_.back(), nullptr); + + writer_stat_guids_.push_back(statistics::to_statistics_type(writers_.back()->guid())); + + std::cout << "Created datawriter " << writers_.back()->guid() << " for topic " + << topics_.back()->get_name() << std::endl; + } + + void create_and_add_reader( + const DataReaderQos& qos = DATAREADER_QOS_DEFAULT) + { + DataReaderQos dr_qos{qos}; + + if (enable_datasharing) + { + dr_qos.data_sharing().automatic(); + } + else + { + dr_qos.data_sharing().off(); + } + + readers_.emplace_back(subscriber_->create_datareader(topics_.back(), dr_qos, &listener_)); + ASSERT_NE(readers_.back(), nullptr); + + reader_stat_guids_.push_back(statistics::to_statistics_type(readers_.back()->guid())); + + std::cout << "Created datareader " << readers_.back()->guid() << " for topic " + << topics_.back()->get_name() << std::endl; + } + + bool delete_writer() + { + if (!writers_.empty()) + { + writer_stat_guids_.pop_back(); + return (eprosima::fastdds::dds::RETCODE_OK == publisher_->delete_datawriter(writers_.back())); + } + + return false; + } + + bool delete_reader() + { + if (!readers_.empty()) + { + reader_stat_guids_.pop_back(); + return (eprosima::fastdds::dds::RETCODE_OK == subscriber_->delete_datareader(readers_.back())); + } + + return false; + } + + void send( + std::list& msgs, + uint32_t milliseconds = 0) + { + auto it = msgs.begin(); + + bool writing_failed {false}; + while (it != msgs.end() && !writing_failed) + { + for (auto& writer : writers_) + { + if (RETCODE_OK == writer->write((void*)&(*it))) + { + default_send_print(*it); + it = msgs.erase(it); + if (milliseconds > 0) + { + std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); + } + } + else + { + writing_failed = true; + break; + } + } + } + } + + bool write_sample( + HelloWorld& sample) + { + if (!writers_.empty()) + { + return (RETCODE_OK == writers_.back()->write(&sample)); + } + + return false; + } + + void reset() + { + ASSERT_EQ(eprosima::fastdds::dds::RETCODE_OK, statistics_part_->delete_contained_entities()); + ASSERT_EQ(eprosima::fastdds::dds::RETCODE_OK, + DomainParticipantFactory::get_instance()->delete_participant(statistics_part_)); + statistics_part_ = nullptr; + } + +protected: + + statistics::dds::DomainParticipant* statistics_part_; + + Publisher* publisher_; + Subscriber* subscriber_; + + std::vector topics_; + + std::vector writers_; + std::vector readers_; + + StatisticsGUIDList writer_stat_guids_; + StatisticsGUIDList reader_stat_guids_; + + TypeSupport type_support_; + + struct MonitorServiceParticipantListener : public DataReaderListener, + public DataWriterListener + { + MonitorServiceParticipantListener() + { + cb_counters_.fill(0); + } + + MonitorServiceParticipantListener( + const MonitorServiceParticipantListener& other) + { + std::unique_lock lock(other.mtx_); + this->cb_counters_ = other.cb_counters_; + } + + MonitorServiceParticipantListener( + MonitorServiceParticipantListener&& other) + { + std::unique_lock lock(other.mtx_); + this->cb_counters_ = std::move(other.cb_counters_); + } + + virtual ~MonitorServiceParticipantListener() + { + } + + void on_offered_deadline_missed ( + DataWriter* writer, + const OfferedDeadlineMissedStatus& status) override + { + { + std::unique_lock lock(mtx_); + ++cb_counters_[OFFERED_DEADLINE_MISSED_IDX]; + } + + std::cout << "on_offered_deadline_missed() " << writer->guid() << " total_count " << status.total_count + << std::endl; + } + + void on_offered_incompatible_qos ( + DataWriter* writer, + const OfferedIncompatibleQosStatus& status) override + { + { + std::unique_lock lock(mtx_); + ++cb_counters_[OFFERED_INCOMPATIBLE_QOS_IDX]; + } + + std::cout << "on_offered_incompatible_qos " << writer->guid() << " total_count " << status.total_count + << std::endl; + } + + void on_liveliness_lost ( + DataWriter* writer, + const LivelinessLostStatus& status) override + { + { + std::unique_lock lock(mtx_); + ++cb_counters_[LIVELINESS_LOST_IDX]; + } + + std::cout << "on_liveliness_lost " << writer->guid() << " total_count " << status.total_count << std::endl; + } + + void on_publication_matched ( + DataWriter* writer, + const PublicationMatchedStatus& status) override + { + { + std::unique_lock lock(mtx_); + ++cb_counters_[PUBLICATION_MATCHED_IDX]; + } + + std::cout << "on_publication_matched " << writer->guid() << " total_count " << status.total_count + << std::endl; + } + + void on_requested_deadline_missed ( + DataReader* reader, + const RequestedDeadlineMissedStatus& status) override + { + { + std::unique_lock lock(mtx_); + ++cb_counters_[REQUESTED_DEADLINE_MISSED_IDX]; + } + + std::cout << "on_requested_deadline_missed" << reader->guid() << " total_count " << status.total_count + << std::endl; + } + + void on_requested_incompatible_qos ( + DataReader* reader, + const RequestedIncompatibleQosStatus& status) override + { + { + std::unique_lock lock(mtx_); + ++cb_counters_[REQUESTED_INCOMPATIBLE_QOS_IDX]; + } + + std::cout << "on_requested_incompatible_qos" << reader->guid() << " total_count " << status.total_count + << std::endl; + } + + void on_liveliness_changed ( + DataReader* reader, + const LivelinessChangedStatus& status) override + { + { + std::unique_lock lock(mtx_); + ++cb_counters_[LIVELINESS_CHANGED_IDX]; + } + + std::cout << "on_liveliness_changed " << reader->guid() << " not_alive_count " << status.not_alive_count + << std::endl; + } + + void on_subscription_matched ( + DataReader* reader, + const SubscriptionMatchedStatus& status) override + { + { + std::unique_lock lock(mtx_); + ++cb_counters_[SUBSCRIPTION_MATCHED_IDX]; + } + + std::cout << "on_subscription_matched " << reader->guid() << " total_count " << status.total_count + << std::endl; + } + + void on_sample_lost ( + DataReader* reader, + const SampleLostStatus& status) override + { + { + std::unique_lock lock(mtx_); + ++cb_counters_[SAMPLE_LOST_IDX]; + } + + std::cout << "on_sample_lost " << reader->guid() << " total_count " << status.total_count << std::endl; + } + + const uint32_t& get_cb_count_of( + CallbackIndex cb_idx) + { + std::unique_lock lock(mtx_); + return cb_counters_[cb_idx]; + } + + private: + + mutable std::mutex mtx_; + std::array cb_counters_; + + } + listener_; +}; + +struct SampleValidator +{ + SampleValidator() + : assert_on_non_expected_msgs_(true) + { + validation_mask.set(); + } + + SampleValidator( + std::bitset& val_mask) + : validation_mask(val_mask) + , assert_on_non_expected_msgs_(true) + { + } + + SampleValidator( + std::bitset& val_mask, + bool assert_on_non_expected_msgs) + : validation_mask(val_mask) + , assert_on_non_expected_msgs_(assert_on_non_expected_msgs) + { + } + + //! Avoid declaring it as virtual + //! in order to correctly apply static_cast + void validate( + SampleInfo&, + MonitorServiceType::type&, + std::list&, + std::atomic&, + std::condition_variable&, + statistics::dds::DomainParticipant*&) + { + } + + void update_processed_msgs( + std::list::iterator& it, + std::list& expected_msgs, + std::atomic& processed_count, + std::condition_variable& cv, + bool& was_expected) + { + was_expected = false; + + if (it != expected_msgs.end()) + { + was_expected = true; + expected_msgs.erase(it); + ++processed_count; + cv.notify_one(); + } + else if (assert_on_non_expected_msgs_) + { + ASSERT_TRUE(false); + } + } + + void register_remote_participant_builtin_topic_data( + const ParticipantBuiltinTopicData& data) + { + remote_participants_data_[data.guid] = data; + } + + void register_remote_publication_builtin_topic_data( + const PublicationBuiltinTopicData& data) + { + remote_pulications_data_[data.guid] = data; + } + + void register_remote_subscription_builtin_topic_data( + const SubscriptionBuiltinTopicData& data) + { + remote_subscriptions_data_[data.guid] = data; + } + + void set_assert_optional_remote_data() + { + assert_optional_remote_data_ = true; + } + +protected: + + std::bitset validation_mask; + bool assert_on_non_expected_msgs_; + bool assert_optional_remote_data_{false}; + + std::map remote_participants_data_; + std::map remote_pulications_data_; + std::map remote_subscriptions_data_; + +}; + +class MonitorServiceConsumer : protected PubSubReader +{ + +public: + + MonitorServiceConsumer() + : PubSubReader(statistics::MONITOR_SERVICE_TOPIC, true, true) + , sample_validator_( new SampleValidator()) + , reader_history_kind_(eprosima::fastdds::dds::KEEP_LAST_HISTORY_QOS) + { + + } + + MonitorServiceConsumer( + std::bitset& val_mask) + : PubSubReader(statistics::MONITOR_SERVICE_TOPIC, true, true) + , sample_validator_( new SampleValidator(val_mask)) + , reader_history_kind_(eprosima::fastdds::dds::KEEP_LAST_HISTORY_QOS) + { + + } + + MonitorServiceConsumer( + std::bitset& val_mask, + bool assert_on_non_expected_msgs) + : PubSubReader(statistics::MONITOR_SERVICE_TOPIC, true, true) + , sample_validator_( new SampleValidator(val_mask, assert_on_non_expected_msgs)) + , reader_history_kind_(eprosima::fastdds::dds::KEEP_LAST_HISTORY_QOS) + { + + } + + virtual ~MonitorServiceConsumer() + { + //! reset received function + take_ = read_ = false; + + stopReception(); + destroy(); + + if (nullptr != sample_validator_) + { + std::lock_guard lock(validator_mtx_); + delete sample_validator_; + sample_validator_ = nullptr; + } + } + + GUID_t get_participant_guid() + { + return statistics_part_->guid(); + } + + void init_monitor_service_reader() + { + reliability(eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS); + durability_kind(eprosima::fastdds::dds::TRANSIENT_LOCAL_DURABILITY_QOS); + history_kind(reader_history_kind_); + resource_limits_max_samples(4000); + resource_limits_max_samples_per_instance(100); + resource_limits_max_instances(30); + init(); + + statistics_part_ = statistics::dds::DomainParticipant::narrow(get_participant()); + } + + void init_monitor_service_reader( + const uint32_t& domain_id) + { + reliability(eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS); + durability_kind(eprosima::fastdds::dds::TRANSIENT_LOCAL_DURABILITY_QOS); + history_kind(reader_history_kind_); + history_depth(1); + + participant_ = DomainParticipantFactory::get_instance()->create_participant( + domain_id, + participant_qos_, + &participant_listener_, + eprosima::fastdds::dds::StatusMask::none()); + + init(); + + statistics_part_ = statistics::dds::DomainParticipant::narrow(get_participant()); + } + + MonitorServiceConsumer& set_reader_history_kind( + HistoryQosPolicyKind kind) + { + reader_history_kind_ = kind; + return *this; + } + + SequenceNumber_t start_reception( + const std::list& msgs) + { + return startReception(msgs); + } + + template + size_t block_for_all( + const std::chrono::duration<_Rep, _Period>& time) + { + return PubSubReader::block_for_all(time); + } + + void block_for_all() + { + PubSubReader::block_for_all(); + } + + void stop() + { + destroy(); + } + + unsigned int get_participants_matched() + { + return participant_matched_; + } + + SampleValidator* get_sample_validator() + { + return sample_validator_; + } + +protected: + + void receive_one( + eprosima::fastdds::dds::DataReader* datareader, + bool& returnedValue) override + { + returnedValue = false; + type data; + eprosima::fastdds::dds::SampleInfo info; + + ReturnCode_t success = datareader->take_next_sample((void*)&data, &info); + + if (eprosima::fastdds::dds::RETCODE_OK == success) + { + returnedValue = true; + + if (info.publication_handle != HANDLE_NIL) + { + std::unique_lock lock(mutex_); + + // Check order of changes + LastSeqInfo seq_info{ info.instance_handle, info.sample_identity.writer_guid() }; + ASSERT_LT(last_seq[seq_info], info.sample_identity.sequence_number()); + last_seq[seq_info] = info.sample_identity.sequence_number(); + + { + std::lock_guard guard(validator_mtx_); + if (nullptr != sample_validator_) + { + validator_selector(statistics_part_, sample_validator_, + data.status_kind(), info, data, total_msgs_, current_processed_count_, cv_); + } + } + } + } + } + + SampleValidator* sample_validator_; + + std::mutex validator_mtx_; + + statistics::dds::DomainParticipant* statistics_part_; + + HistoryQosPolicyKind reader_history_kind_; +}; + +struct ProxySampleValidator : public SampleValidator +{ + void validate( + SampleInfo& info, + MonitorServiceType::type& data, + std::list& total_msgs, + std::atomic& processed_count, + std::condition_variable& cv, + statistics::dds::DomainParticipant* participant = nullptr) + { + if (validation_mask[statistics::StatusKind::PROXY] + && info.valid_data + && info.instance_state == eprosima::fastdds::dds::ALIVE_INSTANCE_STATE) + { + auto it = std::find_if(total_msgs.begin(), total_msgs.end(), + [&](const MonitorServiceType::type& elem) + { + return (data.status_kind() == elem.status_kind()) && + data.local_entity() == elem.local_entity(); + }); + + bool msgs_was_expected = false; + update_processed_msgs(it, total_msgs, processed_count, cv, msgs_was_expected); + // If this proxy is not expected, avoid further processing and return + if (!msgs_was_expected) + { + std::cout << "Unexpected proxy " << statistics::to_fastdds_type(data.local_entity()) + << data.status_kind() << std::endl; + return; + } + + GUID_t guid = statistics::to_fastdds_type(data.local_entity()); + + if (!data.value().entity_proxy().empty()) + { + std::cout << "Received Proxy on local_entity " + << statistics::to_fastdds_type(data.local_entity()) << std::endl; + + if (guid.entityId == c_EntityId_RTPSParticipant) + { + eprosima::fastdds::rtps::ParticipantBuiltinTopicData pdata; + + ASSERT_EQ(participant->fill_discovery_data_from_cdr_message(pdata, + data), + eprosima::fastdds::dds::RETCODE_OK); + + auto part_names = participant->get_participant_names(); + auto it_names = + std::find(part_names.begin(), part_names.end(), pdata.participant_name.to_string()); + ASSERT_TRUE(it_names != part_names.end()); + + if (assert_optional_remote_data_) + { + auto it_rpartd = remote_participants_data_.find(guid); + ASSERT_TRUE(it_rpartd != remote_participants_data_.end()); + ASSERT_EQ(it_rpartd->second.wire_protocol.value(), pdata.wire_protocol.value()); + } + } + else if (guid.entityId.is_reader()) + { + SubscriptionBuiltinTopicData sub_data; + + ASSERT_EQ(participant->fill_discovery_data_from_cdr_message(sub_data, + data), + eprosima::fastdds::dds::RETCODE_OK); + + if (assert_optional_remote_data_) + { + auto it_rsd = remote_subscriptions_data_.find(guid); + ASSERT_TRUE(it_rsd != remote_subscriptions_data_.end()); + ASSERT_EQ(it_rsd->second.reader_data_lifecycle.value(), sub_data.reader_data_lifecycle.value()); + ASSERT_EQ(it_rsd->second.rtps_reliable_reader.value(), sub_data.rtps_reliable_reader.value()); + ASSERT_EQ(it_rsd->second.reader_resource_limits.value(), + sub_data.reader_resource_limits.value()); + } + + } + else if (guid.entityId.is_writer()) + { + PublicationBuiltinTopicData pub_data; + + ASSERT_EQ(participant->fill_discovery_data_from_cdr_message(pub_data, + data), + eprosima::fastdds::dds::RETCODE_OK); + + if (assert_optional_remote_data_) + { + auto it_rpd = remote_pulications_data_.find(guid); + ASSERT_TRUE(it_rpd != remote_pulications_data_.end()); + ASSERT_EQ(it_rpd->second.writer_data_lifecycle.value(), pub_data.writer_data_lifecycle.value()); + ASSERT_EQ(it_rpd->second.publish_mode.value(), pub_data.publish_mode.value()); + ASSERT_EQ(it_rpd->second.rtps_reliable_writer.value(), pub_data.rtps_reliable_writer.value()); + ASSERT_EQ(it_rpd->second.writer_resource_limits.value(), + pub_data.writer_resource_limits.value()); + } + } + else + { + EPROSIMA_LOG_ERROR(BBTestsMonitorService, "Invalid entity guid " << guid); + } + } + else + { + std::cout << "Received Entity disposal of entity " + << statistics::to_fastdds_type(data.local_entity()) << std::endl; + } + } + else if (validation_mask[statistics::StatusKind::PROXY]) + { + auto it = std::find_if(total_msgs.begin(), total_msgs.end(), + [&](const MonitorServiceType::type& elem) + { + return (data.status_kind() == elem.status_kind()) && + data.local_entity() == elem.local_entity(); + }); + + std::cout << "Received unregistration of instance " + << info.instance_handle << std::endl; + + bool msg_was_expected = false; + update_processed_msgs(it, total_msgs, processed_count, cv, msg_was_expected); + } + } + +}; + +struct ConnectionListSampleValidator : public SampleValidator +{ + void validate( + SampleInfo& info, + MonitorServiceType::type& data, + std::list& total_msgs, + std::atomic& processed_count, + std::condition_variable& cv) + { + if (validation_mask[statistics::StatusKind::CONNECTION_LIST] + && info.valid_data + && info.instance_state == eprosima::fastdds::dds::ALIVE_INSTANCE_STATE) + { + std::cout << "Received ConnectionList on local_entity " + << statistics::to_fastdds_type(data.local_entity()) << std::endl; + + for (auto& connection : data.value().connection_list()) + { + std::cout << "Received Connection: \n\tMode: " << static_cast(connection.mode()) + << "\n\tGuid " << statistics::to_fastdds_type(connection.guid()) << "\n\t" + << "Announced Locators: "; + for (auto& locator : connection.used_locators()) + { + std::cout << "\n\t " << statistics::to_fastdds_type(locator) << std::endl; + } + } + + std::cout << std::endl; + + auto it = std::find_if(total_msgs.begin(), total_msgs.end(), + [&](const MonitorServiceType::type& total_msgs_elem) + { + if ((data.status_kind() == total_msgs_elem.status_kind()) && + (data.local_entity() == total_msgs_elem.local_entity())) + { + //! Check for connections + bool expected_locators_found = true; + for (auto& data_connection : data.value().connection_list()) + { + //! Find the first Connection that matches with the incoming, with same + //! mode and same locators + auto conn_it = std::find_if(total_msgs_elem.value().connection_list().begin(), + total_msgs_elem.value().connection_list().end(), + [&](const statistics::Connection& total_msgs_elem_connection) + { + if (total_msgs_elem_connection.mode() == data_connection.mode() + && total_msgs_elem_connection.guid() == data_connection.guid()) + { + //! check locators + bool same_locators = true; + for (size_t i = 0; + i < total_msgs_elem_connection.announced_locators().size(); i++) + { + bool locator_found = false; + for (size_t j = 0; + j < data_connection.announced_locators().size(); j++) + { + //! provided comparison operator + if (total_msgs_elem_connection.announced_locators()[i] == + data_connection.announced_locators()[j]) + { + locator_found = true; + } + } + + if (!locator_found) + { + EPROSIMA_LOG_ERROR(BBTestsMonitorService, + "Locator not found in sample msg " + << statistics::to_fastdds_type(total_msgs_elem_connection. + announced_locators()[i]) + << " for local entity " + << statistics::to_fastdds_type( + total_msgs_elem.local_entity())); + same_locators = false; + break; + } + } + + return same_locators; + } + else + { + return false; + } + }); + + //! Check return connection is the last (valid connection NOT found) + if (conn_it == total_msgs_elem.value().connection_list().end()) + { + expected_locators_found = false; + break; + } + } + + return expected_locators_found; + } + + return false; + }); + + bool msg_was_expected = false; + update_processed_msgs(it, total_msgs, processed_count, cv, msg_was_expected); + } + } + +}; + +struct IncompatibleQoSSampleValidator : public SampleValidator +{ + void validate( + SampleInfo& info, + MonitorServiceType::type& data, + std::list& total_msgs, + std::atomic& processed_count, + std::condition_variable& cv) + { + if (validation_mask[statistics::StatusKind::INCOMPATIBLE_QOS] + && info.valid_data + && info.instance_state == eprosima::fastdds::dds::ALIVE_INSTANCE_STATE) + { + auto it = std::find_if(total_msgs.begin(), total_msgs.end(), + [&](const MonitorServiceType::type& elem) + { + return (data.status_kind() == elem.status_kind()) && + (data.local_entity() == elem.local_entity()) && + (data.value().incompatible_qos_status().last_policy_id() + == elem.value().incompatible_qos_status().last_policy_id()); + }); + + bool msg_was_expected = false; + update_processed_msgs(it, total_msgs, processed_count, cv, msg_was_expected); + + std::cout << "Received QoS Incompatibility on local_entity " + << statistics::to_fastdds_type(data.local_entity()) + << "\n\tLast policy id: " << data.value().incompatible_qos_status().last_policy_id() + << std::endl; + } + } + +}; + +struct LivelinessLostSampleValidator : public SampleValidator +{ + void validate( + SampleInfo& info, + MonitorServiceType::type& data, + std::list& total_msgs, + std::atomic& processed_count, + std::condition_variable& cv) + { + if (validation_mask[statistics::StatusKind::LIVELINESS_LOST] + && info.valid_data + && info.instance_state == eprosima::fastdds::dds::ALIVE_INSTANCE_STATE) + { + auto it = std::find_if(total_msgs.begin(), total_msgs.end(), + [&](const MonitorServiceType::type& elem) + { + return (data.status_kind() == elem.status_kind()) && + (data.local_entity() == elem.local_entity()) && + (data.value().liveliness_lost_status().total_count() + == elem.value().liveliness_lost_status().total_count()); + }); + + if (assert_on_non_expected_msgs_) + { + ASSERT_NE(it, total_msgs.end()); + } + + bool msg_was_expected = false; + update_processed_msgs(it, total_msgs, processed_count, cv, msg_was_expected); + + std::cout << "Received QoS Incompatibility on local_entity " + << statistics::to_fastdds_type(data.local_entity()) + << "\n\tLiveliness Lost Count: " << data.value().liveliness_lost_status().total_count() + << std::endl; + } + } + +}; + +struct LivelinessChangedSampleValidator : public SampleValidator +{ + void validate( + SampleInfo& info, + MonitorServiceType::type& data, + std::list& total_msgs, + std::atomic& processed_count, + std::condition_variable& cv) + { + if (validation_mask[statistics::StatusKind::LIVELINESS_CHANGED] + && info.valid_data + && info.instance_state == eprosima::fastdds::dds::ALIVE_INSTANCE_STATE) + { + auto it = std::find_if(total_msgs.begin(), total_msgs.end(), + [&](const MonitorServiceType::type& elem) + { + return (data.status_kind() == elem.status_kind()) && + (data.local_entity() == elem.local_entity()) && + (data.value().liveliness_changed_status().not_alive_count() + >= elem.value().liveliness_changed_status().not_alive_count()); + }); + + std::cout << "Received Liveliness Changed on local_entity " + << statistics::to_fastdds_type(data.local_entity()) + << "\n\tNot Alive Count: " << data.value().liveliness_changed_status().not_alive_count() + << std::endl; + + bool msg_was_expected = false; + update_processed_msgs(it, total_msgs, processed_count, cv, msg_was_expected); + } + } + +}; + +struct DeadlineMissedSampleValidator : public SampleValidator +{ + void validate( + SampleInfo& info, + MonitorServiceType::type& data, + std::list& total_msgs, + std::atomic& processed_count, + std::condition_variable& cv) + { + std::cout << "Received Deadline Missed on local_entity " + << statistics::to_fastdds_type(data.local_entity()) + << "\n\tTotal Count: " << data.value().deadline_missed_status().total_count() + << std::endl; + + if (validation_mask[statistics::StatusKind::DEADLINE_MISSED] + && info.valid_data + && info.instance_state == eprosima::fastdds::dds::ALIVE_INSTANCE_STATE) + { + auto it = std::find_if(total_msgs.begin(), total_msgs.end(), + [&](const MonitorServiceType::type& elem) + { + return (data.status_kind() == elem.status_kind()) && + (data.local_entity() == elem.local_entity()) && + (data.value().deadline_missed_status().total_count() + >= elem.value().deadline_missed_status().total_count()); + }); + + std::cout << "Received Deadline Missed on local_entity " + << statistics::to_fastdds_type(data.local_entity()) + << "\n\tTotal Count: " << data.value().deadline_missed_status().total_count() + << std::endl; + + bool msg_was_expected = false; + update_processed_msgs(it, total_msgs, processed_count, cv, msg_was_expected); + } + } + +}; + +struct SampleLostSampleValidator : public SampleValidator +{ + void validate( + SampleInfo& info, + MonitorServiceType::type& data, + std::list& total_msgs, + std::atomic& processed_count, + std::condition_variable& cv) + { + if (validation_mask[statistics::StatusKind::SAMPLE_LOST] + && info.valid_data + && info.instance_state == eprosima::fastdds::dds::ALIVE_INSTANCE_STATE) + { + auto it = std::find_if(total_msgs.begin(), total_msgs.end(), + [&](const MonitorServiceType::type& elem) + { + return (data.status_kind() == elem.status_kind()) && + (data.local_entity() == elem.local_entity()) && + (data.value().sample_lost_status().total_count() >= 1); + }); + + std::cout << "Received Sample Lost on local_entity " + << statistics::to_fastdds_type(data.local_entity()) + << "\n\tLast policy id: " << data.value().sample_lost_status().total_count() + << std::endl; + + + bool msg_was_expected = false; + update_processed_msgs(it, total_msgs, processed_count, cv, msg_was_expected); + } + } + +}; + +struct ExtendedIncompatibleQoSValidator : public SampleValidator +{ + void validate( + SampleInfo& info, + MonitorServiceType::type& data, + std::list& total_msgs, + std::atomic& processed_count, + std::condition_variable& cv) + { + if (validation_mask[statistics::StatusKind::EXTENDED_INCOMPATIBLE_QOS] + && info.valid_data + && info.instance_state == eprosima::fastdds::dds::ALIVE_INSTANCE_STATE) + { + auto it = std::find_if(total_msgs.begin(), total_msgs.end(), + [&](const MonitorServiceType::type& elem) + { + return (data.status_kind() == elem.status_kind()) && + (data.local_entity() == elem.local_entity()) && + (std::is_permutation(data.value().extended_incompatible_qos_status().begin(), + data.value().extended_incompatible_qos_status().end(), + elem.value().extended_incompatible_qos_status().begin())); + }); + + std::cout << "Received Extended Incompatible QoS on local_entity " + << statistics::to_fastdds_type(data.local_entity()); + + for (auto& incompatibility : data.value().extended_incompatible_qos_status()) + { + std::cout << "\n\tAgainst remote GUID: " << statistics::to_fastdds_type(incompatibility.remote_guid()) + << "\n\tIncom. policies: "; + for (auto& policy : incompatibility.current_incompatible_policies()) + { + std::cout << "\n\t\tPolicy: " << policy; + } + } + + std::cout << std::endl; + + bool msg_was_expected = false; + update_processed_msgs(it, total_msgs, processed_count, cv, msg_was_expected); + } + } + +}; + +void validator_selector( + statistics::dds::DomainParticipant* participant, + SampleValidator*& validator, + const statistics::StatusKind::StatusKind status_kind, + SampleInfo& info, + MonitorServiceType::type& data, + std::list& total_msgs, + std::atomic& processed_count, + std::condition_variable& cv) +{ + switch (status_kind) + { + case statistics::StatusKind::PROXY: + { + auto sample_validator = static_cast(validator); + sample_validator->validate(info, data, total_msgs, processed_count, cv, participant); + break; + } + case statistics::StatusKind::CONNECTION_LIST: + { + auto sample_validator = static_cast(validator); + sample_validator->validate(info, data, total_msgs, processed_count, cv); + break; + } + case statistics::StatusKind::INCOMPATIBLE_QOS: + { + auto sample_validator = static_cast(validator); + sample_validator->validate(info, data, total_msgs, processed_count, cv); + break; + } + case statistics::StatusKind::INCONSISTENT_TOPIC: + { + break; + } + case statistics::StatusKind::LIVELINESS_LOST: + { + auto sample_validator = static_cast(validator); + sample_validator->validate(info, data, total_msgs, processed_count, cv); + break; + } + case statistics::StatusKind::LIVELINESS_CHANGED: + { + auto sample_validator = static_cast(validator); + sample_validator->validate(info, data, total_msgs, processed_count, cv); + break; + } + case statistics::StatusKind::DEADLINE_MISSED: + { + auto sample_validator = static_cast(validator); + sample_validator->validate(info, data, total_msgs, processed_count, cv); + break; + } + case statistics::StatusKind::SAMPLE_LOST: + { + auto sample_validator = static_cast(validator); + sample_validator->validate(info, data, total_msgs, processed_count, cv); + break; + } + case statistics::StatusKind::EXTENDED_INCOMPATIBLE_QOS: + { + auto sample_validator = static_cast(validator); + sample_validator->validate(info, data, total_msgs, processed_count, cv); + break; + } + default: + break; + } +} + +#endif //FASTDDS_STATISTICS + +/* + * Abbreviations + * +--------+----------------------------+ + * | Abbr | Description | + * +--------+----------------------------+ + * | MS | Monitor Service | + * +--------+----------------------------+ + * | MSC | Monitor Service Consumer | + * +--------+----------------------------+ + * | MSP | Monitor Service Participant| + * +--------+----------------------------+ + * | MSP | Monitor Service Topic | + * +--------+----------------------------+ + */ + +/** + * Refers to DDS-MS-API-01 from the test plan. + * + * Check enable() disable() operations + */ +TEST(DDSMonitorServiceTest, monitor_service_enable_disable_api) +{ +#ifdef FASTDDS_STATISTICS + //! Setup + MonitorServiceParticipant MSP; + + //! Procedure + MSP.setup(); + + //! Assertions + ASSERT_EQ(eprosima::fastdds::dds::RETCODE_NOT_ENABLED, MSP.disable_monitor_service()); + ASSERT_EQ(eprosima::fastdds::dds::RETCODE_OK, MSP.enable_monitor_service()); + ASSERT_EQ(eprosima::fastdds::dds::RETCODE_OK, MSP.disable_monitor_service()); + ASSERT_EQ(eprosima::fastdds::dds::RETCODE_NOT_ENABLED, MSP.disable_monitor_service()); +#endif //FASTDDS_STATISTICS +} + +/** + * Refers to DDS-MS-API-02 from the test plan. + * + * Checks fastdds.enable_monitor_service property and fastdds.statistics with the MONITOR_SERVICE_TOPIC + */ +TEST(DDSMonitorServiceTest, monitor_service_property) +{ +#ifdef FASTDDS_STATISTICS + //! Setup + std::string xml_file = "MonitorServiceDomainParticipant_profile.xml"; + std::pair participant_profile_names = { + "monitor_service_property_participant", "monitor_service_statistics_property_participant" }; + + MonitorServiceParticipant MSP; + + //! Procedure + MSP.setup(xml_file, participant_profile_names.first, 0); + ASSERT_EQ(eprosima::fastdds::dds::RETCODE_OK, MSP.disable_monitor_service()); + + MSP.reset(); + MSP.setup(xml_file, participant_profile_names.second, 0); + + //! Assertions + ASSERT_EQ(eprosima::fastdds::dds::RETCODE_OK, MSP.disable_monitor_service()); +#endif //FASTDDS_STATISTICS +} + +/** + * Refers to DDS-MS-API-03 from the test plan. + * + * Checks that appending MONITOR_SERVICE_TOPIC reserved name + * in FASTDDS_STATISTICS enviroment variable properly initializes + * the service. + */ +TEST(DDSMonitorServiceTest, monitor_service_environment_variable) +{ +#ifdef FASTDDS_STATISTICS + //! Set environment variable and create participant using Qos set by code + const char* value = "NETWORK_LATENCY_TOPIC;MONITOR_SERVICE_TOPIC"; + + #ifdef _WIN32 + ASSERT_EQ(0, _putenv_s("FASTDDS_STATISTICS", value)); + #else + ASSERT_EQ(0, setenv("FASTDDS_STATISTICS", value, 1)); + #endif // ifdef _WIN32 + + //! Setup + MonitorServiceParticipant MSP; + + //! Procedure + MSP.setup(); + + //! Assertions + ASSERT_EQ(eprosima::fastdds::dds::RETCODE_OK, MSP.disable_monitor_service()); +#endif //FASTDDS_STATISTICS +} + +/** + * Refers to DDS-MS-API-04 from the test plan. + * + * Appending the fastdds.enable_monitor_service to the DomainParticipant + * properties in the C++ API correctly creates a MSP. + */ +TEST(DDSMonitorServiceTest, monitor_service_properties_cpp_api) +{ +#ifdef FASTDDS_STATISTICS + //! Setup + MonitorServiceParticipant MSP; + + //! Procedure + DomainParticipantQos pqos; + pqos.properties().properties().push_back({"fastdds.enable_monitor_service", "true"}); + MSP.setup(pqos); + + //! Assertions + ASSERT_EQ(eprosima::fastdds::dds::RETCODE_OK, MSP.disable_monitor_service()); +#endif //FASTDDS_STATISTICS +} + +/** + * Refers to DDS-MS-SIMPLE-01 from the test plan. + * + * A MSC correctly shall receive the corresponding proxy update in the MST after creating + * an endpoint in a MSP + */ +TEST(DDSMonitorServiceTest, monitor_service_simple_proxy) +{ +#ifdef FASTDDS_STATISTICS + + //! Validate PROXY samples only + std::bitset validation_mask; + validation_mask[statistics::StatusKind::PROXY] = true; + + //! Setup + MonitorServiceParticipant MSP; + MonitorServiceConsumer MSC(validation_mask); + + MSP.setup(); + + //! Procedure + MSC.init_monitor_service_reader(); + MSP.enable_monitor_service(); + + std::list expected_msgs; + + MonitorServiceType::type participant_proxy_msg, writer_proxy_msg; + + participant_proxy_msg.status_kind(eprosima::fastdds::statistics::StatusKind::PROXY); + participant_proxy_msg.local_entity(MSP.get_participant_guid()); + + expected_msgs.push_back(participant_proxy_msg); + + MSP.create_and_add_writer(); + + writer_proxy_msg.status_kind(eprosima::fastdds::statistics::StatusKind::PROXY); + StatisticsGUIDList guids = MSP.get_writer_guids(); + + ASSERT_EQ(guids.size(), 1u); + writer_proxy_msg.local_entity(guids.back()); + + expected_msgs.push_back(writer_proxy_msg); + + MSC.start_reception(expected_msgs); + + //! Assertions + //! The assertion checking whether the on_data_availble() was called is assumed in the following one + ASSERT_EQ(MSC.block_for_all(std::chrono::seconds(3)), expected_msgs.size()); +#endif //FASTDDS_STATISTICS +} + +/** + * Refers to DDS-MS-SIMPLE-02 from the test plan. + * + * A MSC correctly shall receive the corresponding connection list update in the MST + * after creating two MSP with a pair of matched endpoints. + * + * TODO: Extend the connection list test cases (or make it TEST_P) + * in order to check for intraprocess and datasharing + * connection modes + * + */ +TEST(DDSMonitorServiceTest, monitor_service_simple_connection_list) +{ +#ifdef FASTDDS_STATISTICS + //! Validate CONNECTION_LIST samples only + std::bitset validation_mask; + validation_mask[statistics::StatusKind::CONNECTION_LIST] = true; + + //! Setup + MonitorServiceParticipant MSP1, MSP2; + MonitorServiceConsumer MSC(validation_mask); + + //! Procedure + //force domain id 0 to share at least the multicast port set in the + //xml config port mapping + MSC.init_monitor_service_reader(0); + + std::string xml_profile = "MonitorServiceConnectionList_profile.xml"; + std::pair participant_profiles = + {"monitor_service_connections_list_participant_1", "monitor_service_connections_list_participant_2"}; + + MSP1.setup(xml_profile, participant_profiles.first, 0); + MSP2.setup(xml_profile, participant_profiles.second, 0); + + MSP1.enable_monitor_service(); + MSP2.enable_monitor_service(); + + std::list expected_msgs; + + MonitorServiceType::type participant_connection_msg, endpoint_connections_msg; + + participant_connection_msg.status_kind(eprosima::fastdds::statistics::StatusKind::CONNECTION_LIST); + participant_connection_msg.local_entity(MSP1.get_participant_guid()); + + std::vector locators; + statistics::detail::Locator_s loc; + + //! For the participant, assert the unicast locators + constexpr const char* LOCAL_ADDRESS = "127.0.0.1"; + constexpr const char* MULTICAST_ADDRESS = "239.255.0.1"; + Locator_t metatraffic_unicast_local_addr_locator(LOCATOR_KIND_UDPv4, 7399); + IPLocator::setIPv4(metatraffic_unicast_local_addr_locator, LOCAL_ADDRESS); + Locator_t default_unicast_local_addr_locator(LOCATOR_KIND_UDPv4, 2020); + IPLocator::setIPv4(default_unicast_local_addr_locator, LOCAL_ADDRESS); + Locator_t metatraffic_multicast_addr_locator(LOCATOR_KIND_UDPv4, 7400); + IPLocator::setIPv4(metatraffic_multicast_addr_locator, MULTICAST_ADDRESS); + + std::vector connection_list; + statistics::Connection conn; + + conn.guid() = statistics::to_statistics_type(MSC.get_participant_guid()); + conn.mode() = statistics::ConnectionMode::TRANSPORT; + Locator_t msc_locator_unicast(LOCATOR_KIND_UDPv4, 7410); + IPLocator::setIPv4(msc_locator_unicast, LOCAL_ADDRESS); + locators.push_back(statistics::to_statistics_type(msc_locator_unicast)); + msc_locator_unicast.port = 7411; + locators.push_back(statistics::to_statistics_type(msc_locator_unicast)); + conn.announced_locators(locators); + connection_list.push_back(conn); + + conn.guid() = MSP2.get_participant_guid(); + conn.mode() = statistics::ConnectionMode::TRANSPORT; + locators.clear(); + locators.push_back(statistics::to_statistics_type(metatraffic_unicast_local_addr_locator)); + locators.push_back(statistics::to_statistics_type(default_unicast_local_addr_locator)); + // The avoid_builtin_multicast prevents the multicast locator list to be filled + // in the participant proxy, avoid expecting it + //locators.push_back(statistics::to_statistics_type(metatraffic_multicast_addr_locator)); + conn.announced_locators(locators); + connection_list.push_back(conn); + + participant_connection_msg.value().connection_list(connection_list); + + expected_msgs.push_back(participant_connection_msg); + + participant_connection_msg.status_kind(eprosima::fastdds::statistics::StatusKind::CONNECTION_LIST); + participant_connection_msg.local_entity(MSP2.get_participant_guid()); + + ASSERT_FALSE(participant_connection_msg.value().connection_list().empty()); + ASSERT_EQ(participant_connection_msg.value().connection_list().back().announced_locators().size(), 2u); + + participant_connection_msg.value().connection_list().back().guid() = MSP1.get_participant_guid(); + participant_connection_msg.value().connection_list().back().mode() = statistics::ConnectionMode::TRANSPORT; + participant_connection_msg.value().connection_list().back().announced_locators()[0].port() = 7398;//unicast + participant_connection_msg.value().connection_list().back().announced_locators()[1].port() = 2019;//unicast + + expected_msgs.push_back(participant_connection_msg); + + DataReaderQos dr_qos; + dr_qos.reliability().kind = eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS; + MSP1.create_and_add_writer(); + MSP2.create_and_add_reader(dr_qos); + + StatisticsGUIDList w_guids, r_guids; + endpoint_connections_msg.status_kind(eprosima::fastdds::statistics::StatusKind::CONNECTION_LIST); + w_guids = MSP1.get_writer_guids(); + ASSERT_EQ(w_guids.size(), 1); + r_guids = MSP2.get_reader_guids(); + ASSERT_EQ(r_guids.size(), 1); + + //! dw and dr have one connection only (with each other) + endpoint_connections_msg.local_entity(w_guids.back()); + endpoint_connections_msg.value().connection_list(connection_list); + endpoint_connections_msg.value().connection_list().pop_back(); + endpoint_connections_msg.value().connection_list().back().guid() = r_guids.back(); + endpoint_connections_msg.value().connection_list().back().mode() = statistics::ConnectionMode::TRANSPORT; + endpoint_connections_msg.value().connection_list().back().announced_locators()[0].port() = 2020;//unicast + metatraffic_multicast_addr_locator.port = 2022; + endpoint_connections_msg.value().connection_list().back().announced_locators()[1] = + statistics::to_statistics_type(metatraffic_multicast_addr_locator); //multicast + + expected_msgs.push_back(endpoint_connections_msg); + + //! An stateful reader only announces its unicast locator + //! An stateless reader, nothing, as the writer does not need to communicate with it + endpoint_connections_msg.local_entity(r_guids.back()); + endpoint_connections_msg.value().connection_list().back().announced_locators().pop_back(); + endpoint_connections_msg.value().connection_list().back().guid() = w_guids.back(); + endpoint_connections_msg.value().connection_list().back().mode() = statistics::ConnectionMode::TRANSPORT; + endpoint_connections_msg.value().connection_list().back().announced_locators()[0].port() = 2019;//unicast + + expected_msgs.push_back(endpoint_connections_msg); + + MSC.start_reception(expected_msgs); + + //! Assertions + //! The assertion checking whether the on_data_availble() was called is assumed in the following one + ASSERT_EQ(MSC.block_for_all(std::chrono::seconds(3)), expected_msgs.size()); +#endif //FASTDDS_STATISTICS +} + +/** + * Refers to DDS-MS-SIMPLE-03 from the test plan. + * + * MSC correctly receives a QoS incompatibility after adding a pair of reader/writer + */ +TEST(DDSMonitorServiceTest, monitor_service_simple_qos_incompatibility_status) +{ +#ifdef FASTDDS_STATISTICS + //! Validate INCOMPATIBLE_QOS samples only + std::bitset validation_mask; + validation_mask[statistics::StatusKind::INCOMPATIBLE_QOS] = true; + + //! Setup + MonitorServiceParticipant MSP; + //! We may receive EXTENDED_INCOMPATIBLE_QOS samples as well + //! prevent them from being validated + MonitorServiceConsumer MSC(validation_mask, false); + + //! Procedure + MSC.init_monitor_service_reader(); + + MSP.setup(); + + MSP.enable_monitor_service(); + + DataReaderQos dr_qos; + DataWriterQos dw_qos; + + dr_qos.reliability().kind = eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS; + dw_qos.reliability().kind = eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS; + + MSP.create_and_add_writer(dw_qos); + MSP.create_and_add_reader(dr_qos); + + std::list expected_msgs; + MonitorServiceType::type endpoint_qos_msg; + StatisticsGUIDList w_guids, r_guids; + + endpoint_qos_msg.status_kind(eprosima::fastdds::statistics::StatusKind::INCOMPATIBLE_QOS); + w_guids = MSP.get_writer_guids(); + ASSERT_EQ(w_guids.size(), 1); + endpoint_qos_msg.local_entity(w_guids.back()); + + statistics::IncompatibleQoSStatus_s incompatible_qos; + statistics::QosPolicyCount_s policy; + policy.policy_id(RELIABILITY_QOS_POLICY_ID); + incompatible_qos.policies().push_back(policy); + endpoint_qos_msg.value().incompatible_qos_status(incompatible_qos); + endpoint_qos_msg.value().incompatible_qos_status().last_policy_id() = RELIABILITY_QOS_POLICY_ID; + expected_msgs.push_back(endpoint_qos_msg); + + endpoint_qos_msg.status_kind(eprosima::fastdds::statistics::StatusKind::INCOMPATIBLE_QOS); + r_guids = MSP.get_reader_guids(); + ASSERT_EQ(r_guids.size(), 1); + endpoint_qos_msg.local_entity(r_guids.back()); + + expected_msgs.push_back(endpoint_qos_msg); + + MSC.start_reception(expected_msgs); + //! Assertions + //! The assertion checking whether the on_data_availble() was called is assumed in the following one + ASSERT_EQ(MSC.block_for_all(std::chrono::seconds(3)), expected_msgs.size()); +#endif //FASTDDS_STATISTICS +} + +/** + * Refers to DDS-MS-SIMPLE-04 from the test plan. + * + * To implement when InconsistentTopciStatus is fully supported + + TEST(DDSMonitorServiceTest, monitor_service_simple_inconsistent_topic) + { + } + */ + +/** + * Refers to DDS-MS-SIMPLE-05 from the test plan. + * + * The lease duration of a writer created in the MSP expires and the liveliness lost status + * is correctly notified to the MSC + */ +TEST(DDSMonitorServiceTest, monitor_service_simple_liveliness_lost_status) +{ +#ifdef FASTDDS_STATISTICS + //! Validate LIVELINESS_LOST samples only + std::bitset validation_mask; + validation_mask[statistics::StatusKind::LIVELINESS_LOST] = true; + + //! Setup + MonitorServiceParticipant MSP; + MonitorServiceConsumer MSC(validation_mask); + + //! Procedure + MSC.init_monitor_service_reader(); + + MSP.setup(); + + MSP.enable_monitor_service(); + + DataWriterQos dw_qos; + + dw_qos.liveliness().kind = eprosima::fastdds::dds::MANUAL_BY_TOPIC_LIVELINESS_QOS; + dw_qos.liveliness().lease_duration = eprosima::fastdds::dds::Time_t{1, 0}; + + MSP.create_and_add_writer(dw_qos); + MSP.assert_liveliness(); + + std::list expected_msgs; + MonitorServiceType::type endpoint_liveliness_msg; + StatisticsGUIDList w_guids; + + endpoint_liveliness_msg.status_kind(eprosima::fastdds::statistics::StatusKind::LIVELINESS_LOST); + w_guids = MSP.get_writer_guids(); + ASSERT_EQ(w_guids.size(), 1); + endpoint_liveliness_msg.local_entity(w_guids.back()); + + statistics::LivelinessLostStatus_s liv_lost_status; + liv_lost_status.total_count() = 1; + endpoint_liveliness_msg.value().liveliness_lost_status(liv_lost_status); + + expected_msgs.push_back(endpoint_liveliness_msg); + + MSC.start_reception(expected_msgs); + + //! Assertions + //! The assertion checking whether the on_data_availble() was called is assumed in the following one + ASSERT_EQ(MSC.block_for_all(std::chrono::seconds(3)), expected_msgs.size()); + ASSERT_TRUE(MSP.get_cb_count(MonitorServiceParticipant::LIVELINESS_LOST_IDX) > 0); +#endif //FASTDDS_STATISTICS +} + +/** + * Refers to DDS-MS-SIMPLE-06 from the test plan. + * + * In a MSP, the liveliness of a reader changes and the status is correctly notified to the + * MSC. + */ +TEST(DDSMonitorServiceTest, monitor_service_simple_liveliness_changed_status) +{ +#ifdef FASTDDS_STATISTICS + //! Validate LIVELINESS_CHANGED samples only + std::bitset validation_mask; + validation_mask[statistics::StatusKind::LIVELINESS_CHANGED] = true; + + //! Setup + MonitorServiceParticipant MSP; + MonitorServiceConsumer MSC(validation_mask); + + //! Procedure + MSC.init_monitor_service_reader(); + + MSP.setup(); + + MSP.enable_monitor_service(); + + DataReaderQos dr_qos; + + dr_qos.reliability().kind = eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS; + dr_qos.liveliness().kind = eprosima::fastdds::dds::MANUAL_BY_TOPIC_LIVELINESS_QOS; + dr_qos.liveliness().lease_duration = eprosima::fastdds::dds::Time_t{1, 0}; + + DataWriterQos dw_qos; + + dw_qos.liveliness().kind = eprosima::fastdds::dds::MANUAL_BY_TOPIC_LIVELINESS_QOS; + dw_qos.liveliness().lease_duration = eprosima::fastdds::dds::Time_t{1, 0}; + + MSP.create_and_add_reader(dr_qos); + MSP.create_and_add_writer(dw_qos); + + MSP.assert_liveliness(); + + std::list expected_msgs; + MonitorServiceType::type endpoint_liveliness_msg; + StatisticsGUIDList r_guids; + + endpoint_liveliness_msg.status_kind(eprosima::fastdds::statistics::StatusKind::LIVELINESS_CHANGED); + r_guids = MSP.get_reader_guids(); + ASSERT_EQ(r_guids.size(), 1); + endpoint_liveliness_msg.local_entity(r_guids.back()); + + statistics::LivelinessChangedStatus_s liv_changed_status; + endpoint_liveliness_msg.value().liveliness_changed_status(liv_changed_status); + + expected_msgs.push_back(endpoint_liveliness_msg); + + MSC.start_reception(expected_msgs); + + //! Assertions + //! The assertion checking whether the on_data_availble() was called is assumed in the following one + ASSERT_EQ(MSC.block_for_all(std::chrono::seconds(5)), expected_msgs.size()); + ASSERT_TRUE(MSP.get_cb_count(MonitorServiceParticipant::LIVELINESS_CHANGED_IDX) > 0); +#endif //FASTDDS_STATISTICS +} + +/** + * Refers to DDS-MS-SIMPLE-07 from the test plan. + * + * The deadline is forced to be missed in an entity of the MSP and the status is correctly + * notified to the MSC. + */ +TEST(DDSMonitorServiceTest, monitor_service_simple_deadline_missed_status) +{ +#ifdef FASTDDS_STATISTICS + + //! Validate DEADLINE_MISSED samples only + std::bitset validation_mask; + validation_mask[statistics::StatusKind::DEADLINE_MISSED] = true; + + //! Setup + MonitorServiceParticipant MSP; + MonitorServiceConsumer MSC(validation_mask); + + //! Procedure + MSC.init_monitor_service_reader(); + + MSP.setup(); + MSP.enable_monitor_service(); + + DataReaderQos dr_qos; + DataWriterQos dw_qos; + + dr_qos.deadline().period = eprosima::fastdds::dds::Time_t{1, 0}; + dw_qos.deadline().period = eprosima::fastdds::dds::Time_t{1, 0}; + + MSP.create_and_add_reader(dr_qos); + MSP.create_and_add_writer(dw_qos); + + std::list expected_msgs; + MonitorServiceType::type endpoint_deadline_msg; + StatisticsGUIDList r_guids, w_guids; + + endpoint_deadline_msg.status_kind(eprosima::fastdds::statistics::StatusKind::DEADLINE_MISSED); + r_guids = MSP.get_reader_guids(); + ASSERT_EQ(r_guids.size(), 1); + endpoint_deadline_msg.local_entity(r_guids.back()); + + statistics::DeadlineMissedStatus_s deadline_missed_status; + deadline_missed_status.total_count() = 1; + endpoint_deadline_msg.value().deadline_missed_status(deadline_missed_status); + + expected_msgs.push_back(endpoint_deadline_msg); + + w_guids = MSP.get_writer_guids(); + ASSERT_EQ(w_guids.size(), 1); + endpoint_deadline_msg.local_entity(w_guids.back()); + + expected_msgs.push_back(endpoint_deadline_msg); + + MSC.start_reception(expected_msgs); + + HelloWorld hello; + ASSERT_TRUE(MSP.write_sample(hello)); + + + //! Assertions + //! The assertion checking whether the on_data_availble() was called is assumed in the following one + ASSERT_EQ(MSC.block_for_all(std::chrono::seconds(3)), expected_msgs.size()); + ASSERT_TRUE(MSP.get_cb_count(MonitorServiceParticipant::OFFERED_DEADLINE_MISSED_IDX) > 0); + ASSERT_TRUE(MSP.get_cb_count(MonitorServiceParticipant::REQUESTED_DEADLINE_MISSED_IDX) > 0); +#endif //FASTDDS_STATISTICS +} + +/** + * Refers to DDS-MS-SIMPLE-08 from the test plan. + * + * Making use of the test transport, force loosing a sample in the MSP so that the lost + * sample status is correctly notified to the MSC. + */ +TEST(DDSMonitorServiceTest, monitor_service_simple_sample_lost_status) +{ +#ifdef FASTDDS_STATISTICS + + //! Validate SAMPLE_LOST samples only + std::bitset validation_mask; + validation_mask[statistics::StatusKind::SAMPLE_LOST] = true; + + //! Setup + MonitorServiceParticipant MSP1, MSP2; + MonitorServiceConsumer MSC(validation_mask); + + //! Procedure + DomainParticipantQos dqos; + + auto testTransport = std::make_shared(); + testTransport->drop_data_messages_filter_ = [](eprosima::fastdds::rtps::CDRMessage_t& msg)-> bool + { + uint32_t old_pos = msg.pos; + + EntityId_t readerID, writerID; + SequenceNumber_t sn; + + msg.pos += 2; // flags + msg.pos += 2; // octets to inline qos + CDRMessage::readEntityId(&msg, &readerID); + CDRMessage::readEntityId(&msg, &writerID); + CDRMessage::readSequenceNumber(&msg, &sn); + + // restore buffer pos + msg.pos = old_pos; + + // generate losses + if ((writerID.value[3] & 0xC0) == 0 // only user endpoints + && (sn == SequenceNumber_t{0, 2} || + sn == SequenceNumber_t(0, 3) || + sn == SequenceNumber_t(0, 4) || + sn == SequenceNumber_t(0, 6) || + sn == SequenceNumber_t(0, 8) || + sn == SequenceNumber_t(0, 10) || + sn == SequenceNumber_t(0, 11) || + sn == SequenceNumber_t(0, 13))) + { + return true; + } + + return false; + }; + + dqos.transport().use_builtin_transports = false; + dqos.transport().user_transports.push_back(testTransport); + + MSC.init_monitor_service_reader(); + + MSP1.setup(dqos); + MSP1.enable_monitor_service(); + + MSP2.setup(); + MSP2.enable_monitor_service(); + + DataReaderQos dr_qos; + DataWriterQos dw_qos; + + MSP1.create_and_add_writer(dw_qos); + MSP2.create_and_add_reader(dr_qos); + + std::list expected_msgs; + MonitorServiceType::type endpoint_sample_lost_msg; + StatisticsGUIDList r_guids; + + endpoint_sample_lost_msg.status_kind(eprosima::fastdds::statistics::StatusKind::SAMPLE_LOST); + r_guids = MSP2.get_reader_guids(); + ASSERT_EQ(r_guids.size(), 1); + endpoint_sample_lost_msg.local_entity(r_guids.back()); + + expected_msgs.push_back(endpoint_sample_lost_msg); + + MSC.start_reception(expected_msgs); + + auto data = default_helloworld_data_generator(); + MSP1.send(data, 50); + + + //! Assertions + //! The assertion checking whether the on_data_availble() was called is assumed in the following one + ASSERT_EQ(MSC.block_for_all(std::chrono::seconds(5)), expected_msgs.size()); + ASSERT_TRUE(MSP2.get_cb_count(MonitorServiceParticipant::SAMPLE_LOST_IDX) > 0); +#endif //FASTDDS_STATISTICS +} + +/** + * Refers to DDS-MS-SIMPLE-09 from the test plan. + * + * Removing a previously created endpoint in the MSP makes MSC to receive the corre- + * sponding instance disposals. + */ +TEST(DDSMonitorServiceTest, monitor_service_simple_instance_disposals) +{ +#ifdef FASTDDS_STATISTICS + + //! Validate PROXY samples only + std::bitset validation_mask; + validation_mask[statistics::StatusKind::PROXY] = true; + + //! Setup + MonitorServiceParticipant MSP; + MonitorServiceConsumer MSC(validation_mask); + + MSP.setup(); + + //! Procedure + MSC.init_monitor_service_reader(); + MSP.enable_monitor_service(); + + MSP.create_and_add_writer(); + + std::list expected_msgs; + + MonitorServiceType::type proxy_msg; + auto writers = MSP.get_writer_guids(); + ASSERT_TRUE(writers.size()); + + //! expect 2 proxies, one empty proxy (disposal) + //! and 8 statuses kind unregistered + proxy_msg.local_entity() = writers.back(); + expected_msgs.push_back(proxy_msg); + + proxy_msg.local_entity() = MSP.get_participant_guid(); + expected_msgs.push_back(proxy_msg); + + //! Expect one unregister for each of the statuses + for (uint32_t i = 0; i < statistics::StatusKind::STATUSES_SIZE; i++) + { + proxy_msg.local_entity() = statistics::to_statistics_type(c_Guid_Unknown); + expected_msgs.push_back(proxy_msg); + } + + MSC.start_reception(expected_msgs); + MSP.delete_writer(); + std::this_thread::sleep_for(std::chrono::seconds(2)); + //! For the moment, until fake instances feature is available + //! expect a discovery callback for the matching status to deduce + //! the dispose of the participant + MSP.reset(); + + //! Assertions + //! The assertion checking whether the on_data_availble() was called is assumed in the following one + ASSERT_EQ(MSC.block_for_all(std::chrono::seconds(3)), expected_msgs.size()); + ASSERT_FALSE(MSC.get_participants_matched()); + +#endif //FASTDDS_STATISTICS +} + +/** + * Refers to DDS-MS-SIMPLE-10 from the test plan. + * + * MSC late joins a MSP with an already enabled MS. + */ +TEST(DDSMonitorServiceTest, monitor_service_simple_late_joiner) +{ +#ifdef FASTDDS_STATISTICS + + //! Validate PROXY samples only + std::bitset validation_mask; + validation_mask[statistics::StatusKind::PROXY] = true; + + //! Setup + MonitorServiceParticipant MSP; + MonitorServiceConsumer MSC(validation_mask); + + MSP.setup(); + + //! Procedure + MSP.enable_monitor_service(); + + std::list expected_msgs; + + MonitorServiceType::type participant_proxy_msg, entity_proxy_msg; + + participant_proxy_msg.status_kind(eprosima::fastdds::statistics::StatusKind::PROXY); + participant_proxy_msg.local_entity(MSP.get_participant_guid()); + + expected_msgs.push_back(participant_proxy_msg); + + MSP.create_and_add_writer(); + MSP.create_and_add_reader(); + + entity_proxy_msg.status_kind(eprosima::fastdds::statistics::StatusKind::PROXY); + StatisticsGUIDList w_guids = MSP.get_writer_guids(); + + ASSERT_EQ(w_guids.size(), 1); + entity_proxy_msg.local_entity(w_guids.back()); + + expected_msgs.push_back(entity_proxy_msg); + + entity_proxy_msg.status_kind(eprosima::fastdds::statistics::StatusKind::PROXY); + StatisticsGUIDList r_guids = MSP.get_reader_guids(); + + ASSERT_EQ(r_guids.size(), 1); + entity_proxy_msg.local_entity(r_guids.back()); + + expected_msgs.push_back(entity_proxy_msg); + + std::this_thread::sleep_for(std::chrono::seconds(5)); + + MSC.init_monitor_service_reader(); + MSC.start_reception(expected_msgs); + + //! Assertions + //! The assertion checking whether the on_data_availble() was called is assumed in the following one + ASSERT_EQ(MSC.block_for_all(std::chrono::seconds(3)), expected_msgs.size()); + +#endif //FASTDDS_STATISTICS +} + +/** + * Refers to DDS-MS-SIMPLE-11 from the test plan. + * + * Enabling the MS, disabling it, making some updates and re-enabling it shall correctly + * behave. + */ +TEST(DDSMonitorServiceTest, monitor_service_simple_enable_disable_enable) +{ +#ifdef FASTDDS_STATISTICS + + //! Validate PROXY samples only + std::bitset validation_mask; + validation_mask[statistics::StatusKind::PROXY] = true; + + //! Setup + MonitorServiceParticipant MSP; + MonitorServiceConsumer MSC(validation_mask); + + //! Procedure + MSP.setup(); + + MSC.init_monitor_service_reader(); + MSP.enable_monitor_service(); + + MSP.create_and_add_writer(); + MSP.disable_monitor_service(); + + MSP.create_and_add_writer(); + MSP.create_and_add_reader(); + + std::list expected_msgs; + + MonitorServiceType::type participant_proxy_msg, entity_proxy_msg; + + participant_proxy_msg.status_kind(eprosima::fastdds::statistics::StatusKind::PROXY); + participant_proxy_msg.local_entity(MSP.get_participant_guid()); + + expected_msgs.push_back(participant_proxy_msg); + + entity_proxy_msg.status_kind(eprosima::fastdds::statistics::StatusKind::PROXY); + StatisticsGUIDList w_guids = MSP.get_writer_guids(); + StatisticsGUIDList r_guids = MSP.get_reader_guids(); + + ASSERT_EQ(w_guids.size(), 2); + entity_proxy_msg.local_entity(w_guids.front()); + expected_msgs.push_back(entity_proxy_msg); + entity_proxy_msg.local_entity(w_guids.back()); + expected_msgs.push_back(entity_proxy_msg); + + ASSERT_EQ(r_guids.size(), 1); + entity_proxy_msg.local_entity(r_guids.back()); + + expected_msgs.push_back(entity_proxy_msg); + + std::this_thread::sleep_for(std::chrono::seconds(1)); + + MSP.enable_monitor_service(); + + MSC.start_reception(expected_msgs); + + //! Assertions + //! The assertion checking whether the on_data_availble() was called is assumed in the following one + ASSERT_EQ(MSC.block_for_all(std::chrono::seconds(3)), expected_msgs.size()); +#endif //FASTDDS_STATISTICS +} + +/** + * Refers to DDS-MS-SIMPLE-11 from the test plan. + * + * A pair of MSPs correctly notify MSC with proper extended QoS incompatibility + * after creating a pair of reader/writer sharing the same topic with an incompatible + * configuration. + */ +TEST(DDSMonitorServiceTest, monitor_service_simple_extended_incompatible_qos) +{ +#ifdef FASTDDS_STATISTICS + using namespace eprosima::fastdds::statistics; + + //! Validate PROXY samples + //! Validate EXTENDED_INCOMPATIBLE_QOS samples + std::bitset validation_mask; + validation_mask[statistics::StatusKind::PROXY] = true; + validation_mask[statistics::StatusKind::EXTENDED_INCOMPATIBLE_QOS] = true; + + //! Setup + MonitorServiceParticipant MSP; + //! We will receive extra msgs such as instance unregistrations but we are not interested in + //! them in this test, so we set assert_on_non_expected_msgs to false + MonitorServiceConsumer MSC(validation_mask, false); + + //! Procedure + MSP.setup(); + + MSC.init_monitor_service_reader(); + MSP.enable_monitor_service(); + + DataReaderQos dr_qos; + DataWriterQos dw_qos; + + dr_qos.reliability().kind = eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS; + dw_qos.reliability().kind = eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS; + + MSP.create_and_add_writer(dw_qos); + MSP.create_and_add_reader(dr_qos); + + std::list expected_msgs; + + // Expected messages for the new entities discovering + MonitorServiceType::type participant_proxy_msg, entity_proxy_msg, endpoint_ext_incmpqos_msg; + + participant_proxy_msg.status_kind(StatusKind::PROXY); + participant_proxy_msg.local_entity(MSP.get_participant_guid()); + + expected_msgs.push_back(participant_proxy_msg); + + entity_proxy_msg.status_kind(StatusKind::PROXY); + StatisticsGUIDList w_guids = MSP.get_writer_guids(); + StatisticsGUIDList r_guids = MSP.get_reader_guids(); + + ASSERT_EQ(w_guids.size(), 1); + entity_proxy_msg.local_entity(w_guids.back()); + expected_msgs.push_back(entity_proxy_msg); + + ASSERT_EQ(r_guids.size(), 1); + entity_proxy_msg.local_entity(r_guids.back()); + expected_msgs.push_back(entity_proxy_msg); + + // Expected messages for the extended qos incompatibilities + endpoint_ext_incmpqos_msg.status_kind(StatusKind::EXTENDED_INCOMPATIBLE_QOS); + endpoint_ext_incmpqos_msg.local_entity(w_guids.back()); + + // Writer extended incompatibility for reliability + statistics::ExtendedIncompatibleQoSStatusSeq_s ext_incompatible_qos_seq; + statistics::ExtendedIncompatibleQoSStatus_s ext_incompatible_qos; + std::vector incompatible_policies; + + ext_incompatible_qos.remote_guid(r_guids.back()); + incompatible_policies.push_back(RELIABILITY_QOS_POLICY_ID); + ext_incompatible_qos.current_incompatible_policies(incompatible_policies); + ext_incompatible_qos_seq.push_back(ext_incompatible_qos); + endpoint_ext_incmpqos_msg.value().extended_incompatible_qos_status(ext_incompatible_qos_seq); + expected_msgs.push_back(endpoint_ext_incmpqos_msg); + + // Reader extended incompatibility for reliability + r_guids = MSP.get_reader_guids(); + ASSERT_EQ(r_guids.size(), 1); + endpoint_ext_incmpqos_msg.local_entity(r_guids.back()); + ext_incompatible_qos_seq.at(0).remote_guid(w_guids.back()); + endpoint_ext_incmpqos_msg.value().extended_incompatible_qos_status(ext_incompatible_qos_seq); + expected_msgs.push_back(endpoint_ext_incmpqos_msg); + + MSC.start_reception(expected_msgs); + + ASSERT_EQ(MSC.block_for_all(std::chrono::seconds(3)), expected_msgs.size()); + expected_msgs.clear(); + + // When the reader is destroyed, the MSP's writer should notify + // with an empty extended incompatible qos list. Add it to + // the expected messages + MonitorServiceType::type empty_ext_incmpqos_msg; + empty_ext_incmpqos_msg.status_kind(StatusKind::EXTENDED_INCOMPATIBLE_QOS); + empty_ext_incmpqos_msg.local_entity(w_guids.back()); + empty_ext_incmpqos_msg.value().extended_incompatible_qos_status(statistics::ExtendedIncompatibleQoSStatusSeq_s()); + expected_msgs.push_back(empty_ext_incmpqos_msg); + + // Additionally, we should receive an empty proxy status kind for the reader + // meaning that the reader has been disposed + MonitorServiceType::type reader_disposal_msg; + reader_disposal_msg.local_entity(r_guids.back()); + expected_msgs.push_back(reader_disposal_msg); + + MSC.start_reception(expected_msgs); + + MSP.delete_reader(); + + ASSERT_EQ(MSC.block_for_all(std::chrono::seconds(3)), expected_msgs.size()); + expected_msgs.clear(); + + dr_qos.reliability().kind = eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS; + MSP.create_and_add_reader(dr_qos); + + // Expect only the new reader's discovery proxy one + r_guids = MSP.get_reader_guids(); + ASSERT_EQ(r_guids.size(), 1); + entity_proxy_msg.local_entity(r_guids.back()); + expected_msgs.push_back(entity_proxy_msg); + + MSC.start_reception(expected_msgs); + + ASSERT_EQ(MSC.block_for_all(std::chrono::seconds(3)), expected_msgs.size()); +#endif //FASTDDS_STATISTICS +} + +/** + * Refers to DDS-MS-ADV-01 from the test plan. + * + * A MSC shall correctly receive the corresponding proxies from different MSPs. + */ +TEST(DDSMonitorServiceTest, monitor_service_advanced_proxy) +{ +#ifdef FASTDDS_STATISTICS + //! Validate PROXY samples only + std::bitset validation_mask; + validation_mask[statistics::StatusKind::PROXY] = true; + + //! Setup + size_t n_participants = 3; + std::vector MSPs; + MSPs.resize(n_participants); + + MonitorServiceConsumer MSC(validation_mask); + + //! Procedure + MSC.init_monitor_service_reader(); + + std::list expected_msgs; + + int topic_idx = 1; + for (auto& MSP : MSPs) + { + MSP.setup(); + MSP.enable_monitor_service(); + + MSP.create_topic(topic_idx); + topic_idx++; + + MSP.create_and_add_writer(); + MSP.create_and_add_reader(); + + MonitorServiceType::type participant_proxy_msg, entity_proxy_msg; + + participant_proxy_msg.status_kind(eprosima::fastdds::statistics::StatusKind::PROXY); + participant_proxy_msg.local_entity(MSP.get_participant_guid()); + + expected_msgs.push_back(participant_proxy_msg); + + entity_proxy_msg.status_kind(eprosima::fastdds::statistics::StatusKind::PROXY); + StatisticsGUIDList w_guids = MSP.get_writer_guids(); + StatisticsGUIDList r_guids = MSP.get_reader_guids(); + + ASSERT_EQ(w_guids.size(), 1); + entity_proxy_msg.local_entity(w_guids.back()); + expected_msgs.push_back(entity_proxy_msg); + + ASSERT_EQ(r_guids.size(), 1); + entity_proxy_msg.local_entity(r_guids.back()); + + expected_msgs.push_back(entity_proxy_msg); + } + + MSC.start_reception(expected_msgs); + + //! Assertions + //! The assertion checking whether the on_data_availble() was called is assumed in the following one + ASSERT_EQ(MSC.block_for_all(std::chrono::seconds(10)), expected_msgs.size()); +#endif //FASTDDS_STATISTICS +} + +/** + * Refers to DDS-MS-ADV-02 from the test plan. + * + * A MSC shall correctly receive the corresponding instance disposals after deleting one + * of the MSP. + */ +TEST(DDSMonitorServiceTest, monitor_service_advanced_instance_disposals) +{ +#ifdef FASTDDS_STATISTICS + //! Setup + size_t n_participants = 3; + std::vector MSPs; + MSPs.resize(n_participants); + + //! Validate PROXY samples only + std::bitset validation_mask; + validation_mask[statistics::StatusKind::PROXY] = true; + + MonitorServiceConsumer MSC (validation_mask); + + //! Procedure + MSC.init_monitor_service_reader(); + + std::list expected_msgs; + + int topic_idx = 1; + for (auto& MSP : MSPs) + { + MonitorServiceType::type msg; + + MSP.setup(); + MSP.enable_monitor_service(); + + MSP.create_topic(topic_idx); + topic_idx++; + + MSP.create_and_add_writer(); + MSP.create_and_add_reader(); + + //! Participant entity + writer + reader proxies + msg.local_entity() = MSP.get_participant_guid(); + expected_msgs.push_back(msg); + msg.local_entity() = MSP.get_reader_guids().back(); + expected_msgs.push_back(msg); + msg.local_entity() = MSP.get_writer_guids().back(); + expected_msgs.push_back(msg); + } + + ASSERT_EQ(3, MSPs.size()); + + //! Expect 6 empty proxies (disposals) (3 entities per each) + for (auto& MSP : MSPs) + { + MonitorServiceType::type msg; + msg.local_entity(MSP.get_reader_guids().back()); + expected_msgs.push_back(msg); + msg.local_entity(MSP.get_writer_guids().back()); + expected_msgs.push_back(msg); + } + + //! Plus 48 instance disposals (8 per instance) + for (uint32_t i = 0; i < 6 * statistics::StatusKind::STATUSES_SIZE; i++) + { + expected_msgs.push_back(MonitorServiceType::type()); + } + + MSC.start_reception(expected_msgs); + + //! Give some time to receive the proxies + //! Otherwise, if we suddendy delete the entities, the monitor + //! service will not say nothing about that entity and the + //! expected_msgs list will not be correct + std::this_thread::sleep_for(std::chrono::seconds(3)); + + for (auto& MSP : MSPs) + { + MSP.delete_reader(); + MSP.delete_writer(); + } + + //! Assertions + //! The assertion checking whether the on_data_availble() was called is assumed in the following one + ASSERT_EQ(MSC.block_for_all(std::chrono::seconds(5)), expected_msgs.size()); +#endif //FASTDDS_STATISTICS +} + +/** + * Refers to DDS-MS-ADV-03 from the test plan. + * + * A MSC shall correctly receive the updates after late joining. + */ +TEST(DDSMonitorServiceTest, monitor_service_advanced_single_late_joiner) +{ +#ifdef FASTDDS_STATISTICS + + //! Validate INCOMPATIBLE_QOS samples only + std::bitset validation_mask; + validation_mask[statistics::StatusKind::INCOMPATIBLE_QOS] = true; + + //! Setup + size_t n_participants = 3; + std::vector MSPs; + MSPs.resize(n_participants); + //! We may receive EXTENDED_INCOMPATIBLE_QOS samples as well + //! prevent them from being validated + MonitorServiceConsumer MSC(validation_mask, false); + + //! Procedure + std::list expected_msgs; + + int topic_idx = 1; + for (auto& MSP : MSPs) + { + MSP.setup(); + MSP.enable_monitor_service(); + + DataReaderQos dr_qos; + DataWriterQos dw_qos; + + dr_qos.reliability().kind = eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS; + dw_qos.reliability().kind = eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS; + + MSP.create_topic(topic_idx); + topic_idx++; + + MSP.create_and_add_writer(dw_qos); + MSP.create_and_add_reader(dr_qos); + + MonitorServiceType::type endpoint_qos_msg; + StatisticsGUIDList w_guids, r_guids; + + endpoint_qos_msg.status_kind(eprosima::fastdds::statistics::StatusKind::INCOMPATIBLE_QOS); + w_guids = MSP.get_writer_guids(); + ASSERT_EQ(w_guids.size(), 1); + endpoint_qos_msg.local_entity(w_guids.back()); + + statistics::IncompatibleQoSStatus_s incompatible_qos; + incompatible_qos.last_policy_id(RELIABILITY_QOS_POLICY_ID); + endpoint_qos_msg.value().incompatible_qos_status(incompatible_qos); + + expected_msgs.push_back(endpoint_qos_msg); + + endpoint_qos_msg.status_kind(eprosima::fastdds::statistics::StatusKind::INCOMPATIBLE_QOS); + r_guids = MSP.get_reader_guids(); + ASSERT_EQ(r_guids.size(), 1); + endpoint_qos_msg.local_entity(r_guids.back()); + + expected_msgs.push_back(endpoint_qos_msg); + } + + std::this_thread::sleep_for(std::chrono::seconds(5)); + MSC.init_monitor_service_reader(); + MSC.start_reception(expected_msgs); + + //! Assertions + //! The assertion checking whether the on_data_availble() was called is assumed in the following one + ASSERT_EQ(MSC.block_for_all(std::chrono::seconds(5)), expected_msgs.size()); +#endif //FASTDDS_STATISTICS +} + +/** + * Refers to DDS-MS-ADV-04 from the test plan. + * + * Multiple MSC shall correctly receive the updates after late joining. + */ +TEST(DDSMonitorServiceTest, monitor_service_advanced_multiple_late_joiners) +{ +#ifdef FASTDDS_STATISTICS + //! Setup + MonitorServiceParticipant MSP; + size_t n_participants = 3; + + //! Procedure + std::list expected_msgs; + + MSP.setup(); + MSP.enable_monitor_service(); + + DataReaderQos dr_qos; + DataWriterQos dw_qos; + + dr_qos.reliability().kind = eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS; + dw_qos.reliability().kind = eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS; + + MSP.create_and_add_writer(dw_qos); + MSP.create_and_add_reader(dr_qos); + + MonitorServiceType::type endpoint_qos_msg; + StatisticsGUIDList w_guids, r_guids; + + endpoint_qos_msg.status_kind(eprosima::fastdds::statistics::StatusKind::INCOMPATIBLE_QOS); + w_guids = MSP.get_writer_guids(); + ASSERT_EQ(w_guids.size(), 1); + endpoint_qos_msg.local_entity(w_guids.back()); + + statistics::IncompatibleQoSStatus_s incompatible_qos; + incompatible_qos.last_policy_id(RELIABILITY_QOS_POLICY_ID); + endpoint_qos_msg.value().incompatible_qos_status(incompatible_qos); + + expected_msgs.push_back(endpoint_qos_msg); + + endpoint_qos_msg.status_kind(eprosima::fastdds::statistics::StatusKind::INCOMPATIBLE_QOS); + r_guids = MSP.get_reader_guids(); + ASSERT_EQ(r_guids.size(), 1); + endpoint_qos_msg.local_entity(r_guids.back()); + + expected_msgs.push_back(endpoint_qos_msg); + + std::this_thread::sleep_for(std::chrono::seconds(3)); + + for (size_t i = 0; i < n_participants; i++) + { + //! Validate INCOMPATIBLE_QOS samples only + std::bitset validation_mask; + validation_mask[statistics::StatusKind::INCOMPATIBLE_QOS] = true; + + MonitorServiceConsumer MSC(validation_mask); + + MSC.init_monitor_service_reader(); + MSC.start_reception(expected_msgs); + + //! Assertions + //! The assertion checking whether the on_data_availble() was called is assumed in the following one + ASSERT_EQ(MSC.block_for_all(std::chrono::seconds(3)), expected_msgs.size()); + } +#endif //FASTDDS_STATISTICS +} + +/** + * Refers to DDS-MS-ADV-05 from the test plan. + * + * Multiple MSPs notifiy MSC with the correct extended QoS incompatibilities. 4 MSPs + * are created, one with a writer and the others with one an incompatible qos reader + * in the same topic. It is checked that the MSC receives the corresponding extended + * incompatibility notifications. + */ + +void add_extended_incompatible_policy( + std::list& expected_msgs, + const statistics::detail::GUID_s& source_entity_guid, + const std::vector>>& remote_entity_guids_and_policies) +{ + MonitorServiceType::type endpoint_ext_incmpqos_msg; + StatisticsGUIDList r_guids; + statistics::ExtendedIncompatibleQoSStatusSeq_s ext_incompatible_qos_seq; + statistics::ExtendedIncompatibleQoSStatus_s ext_incompatible_qos; + + endpoint_ext_incmpqos_msg.status_kind(eprosima::fastdds::statistics::StatusKind::EXTENDED_INCOMPATIBLE_QOS); + endpoint_ext_incmpqos_msg.local_entity(source_entity_guid); + + for (const auto& remote_entity_guid_and_policies : remote_entity_guids_and_policies) + { + ext_incompatible_qos.remote_guid(remote_entity_guid_and_policies.first); + ext_incompatible_qos.current_incompatible_policies(remote_entity_guid_and_policies.second); + ext_incompatible_qos_seq.push_back(ext_incompatible_qos); + } + + endpoint_ext_incmpqos_msg.value().extended_incompatible_qos_status(ext_incompatible_qos_seq); + expected_msgs.push_back(endpoint_ext_incmpqos_msg); +} + +TEST(DDSMonitorServiceTest, monitor_service_advanced_extended_incompatible_qos) +{ +#ifdef FASTDDS_STATISTICS + + //! Validate EXTENDED_INCOMPATIBLE_QOS and PROXY samples only + std::bitset validation_mask; + //! In this test we will only expect the disposal proxy samples, not discovery ones + validation_mask[statistics::StatusKind::PROXY] = true; + validation_mask[statistics::StatusKind::EXTENDED_INCOMPATIBLE_QOS] = true; + + //! Setup + size_t n_participants = 4; + std::vector MSPs; + MSPs.resize(n_participants); + //! By validating PROXY we will receive extra msgs such as discovery proxies or instance unregistrations + //! (We are only interested in the disposals from the PROXY status kind) + //! but we are not interested in them in this test, so we set assert_on_non_expected_msgs to false + MonitorServiceConsumer MSC(validation_mask, false); + MSC.init_monitor_service_reader(); + + //! Procedure + std::list expected_msgs; + + size_t idx = 0; + statistics::detail::GUID_s w_guid; + for (auto& MSP : MSPs) + { + MSP.setup(); + MSP.enable_monitor_service(); + + switch (idx) + { + case 0: + { + DataWriterQos dw_qos; + dw_qos.durability().kind = eprosima::fastdds::dds::VOLATILE_DURABILITY_QOS; + + MSP.create_and_add_writer(dw_qos); + + ASSERT_EQ(MSP.get_writer_guids().size(), 1); + w_guid = MSP.get_writer_guids().back(); + + break; + } + case 1: + { + DataReaderQos dr_qos; + dr_qos.durability().kind = TRANSIENT_LOCAL_DURABILITY_QOS; + + MSP.create_and_add_reader(dr_qos); + + //! Add extended qos incompatibilities + //! for the reader (reader -> writer) + add_extended_incompatible_policy( + expected_msgs, + MSP.get_reader_guids().back(), + {{w_guid, {DURABILITY_QOS_POLICY_ID}}} + ); + + break; + } + case 2: + { + DataReaderQos dr_qos; + dr_qos.liveliness().kind = eprosima::fastdds::dds::MANUAL_BY_PARTICIPANT_LIVELINESS_QOS; + + MSP.create_and_add_reader(dr_qos); + + //! Add extended qos incompatibilities + //! for the reader (reader -> writer) + add_extended_incompatible_policy( + expected_msgs, + MSP.get_reader_guids().back(), + {{w_guid, {LIVELINESS_QOS_POLICY_ID}}} + ); + + break; + } + case 3: + { + DataReaderQos dr_qos; + dr_qos.ownership().kind = eprosima::fastdds::dds::EXCLUSIVE_OWNERSHIP_QOS; + + MSP.create_and_add_reader(dr_qos); + + //! Add extended qos incompatibilities + //! for the reader (reader -> writer) + add_extended_incompatible_policy( + expected_msgs, + MSP.get_reader_guids().back(), + {{w_guid, {OWNERSHIP_QOS_POLICY_ID}}} + ); + + break; + } + } + + idx++; + } + + //! Finally, add the extended incompatibility for the writer + //! Note: we may receive intermediate incompatibility messages like + //! {MSPs[1].get_reader_guids().back(), {DURABILITY_QOS_POLICY_ID}}, + //! and + //! {MSPs[1].get_reader_guids().back(), {DURABILITY_QOS_POLICY_ID}}, + //! {MSPs[2].get_reader_guids().back(), {LIVELINESS_QOS_POLICY_ID}}, + //! but the important thing is that we receive the folowing one + add_extended_incompatible_policy( + expected_msgs, + w_guid, + { + {MSPs[1].get_reader_guids().back(), {DURABILITY_QOS_POLICY_ID}}, + {MSPs[2].get_reader_guids().back(), {LIVELINESS_QOS_POLICY_ID}}, + {MSPs[3].get_reader_guids().back(), {OWNERSHIP_QOS_POLICY_ID}}}); + + MSC.start_reception(expected_msgs); + + //! Assertions + MSC.block_for_all(); + + expected_msgs.clear(); + + //! Now we are about to remove readers, prepare the expected messages. + //! Empty extended incompatible qos lists + add_extended_incompatible_policy( + expected_msgs, + w_guid, + {}); + + //! readers disposals + for (size_t i = 1; i < n_participants; i++) + { + MonitorServiceType::type reader_disposal_msg; + reader_disposal_msg.local_entity(MSPs[i].get_reader_guids().back()); + expected_msgs.push_back(reader_disposal_msg); + } + + MSC.start_reception(expected_msgs); + + for (size_t i = 1; i < n_participants; i++) + { + MSPs[i].delete_reader(); + } + + //! Assertions + MSC.block_for_all(); + +#endif //FASTDDS_STATISTICS +} + +/** + * Regression test for Redmine issue #20625. + * + * The monitor service writer must correctly handle instances, removing old changes + * when its going to publish a new change in the same instance since it is + * defined as KEEP_LAST 1. + */ +TEST(DDSMonitorServiceTest, monitor_service_properly_handles_topic_instances) +{ +#ifdef FASTDDS_STATISTICS + + // In this test we do not need to enforce the validation of samples + std::bitset validation_mask; + + // Setup consumer participant + MonitorServiceConsumer MSC(validation_mask); + + MSC.init_monitor_service_reader(); + + // Setup participant with monitor service enabled + MonitorServiceParticipant MSP; + auto test_transport = std::make_shared(); + + std::atomic n_gap_messages{0}; + + // Prepare filter to intercept gap messages + test_transport->drop_gap_messages_filter_ = [&n_gap_messages](CDRMessage_t& msg) + { + + // Jump the reader entity id + msg.pos += 4; + // Read the writer's entity id + eprosima::fastdds::rtps::EntityId_t writer_entity_id; + eprosima::fastdds::rtps::CDRMessage::readEntityId(&msg, &writer_entity_id); + + if (ENTITYID_MONITOR_SERVICE_WRITER == writer_entity_id) + { + n_gap_messages.fetch_add(1); + } + + return false; + }; + + DomainParticipantQos participant_qos; + participant_qos.transport().user_transports.push_back(test_transport); + participant_qos.transport().use_builtin_transports = false; + + MSP.setup(participant_qos); + MSP.enable_monitor_service(); + + DataReaderQos dr_qos; + DataWriterQos dw_qos; + + //! Set deadline as 1 sec + dr_qos.deadline().period = eprosima::fastdds::dds::Time_t{1, 000000000}; + dw_qos.deadline().period = eprosima::fastdds::dds::Time_t{1, 000000000}; + + MSP.create_and_add_reader(dr_qos); + MSP.create_and_add_writer(dw_qos); + + MSC.start_reception(std::list()); + + auto samples = default_helloworld_data_generator(4); + // Default heartbeat period is 3 secs. + // Ensure that the monitor service writer sends at least one + // waiting 1150 milliseconds between samples + MSP.send(samples, 1150); + + ASSERT_TRUE(n_gap_messages > 0u); +#endif //FASTDDS_STATISTICS +} + +/** + * Regression test for Redmine issue #20625. + * + * A monitor service consumer receives only the last instance status when late joins. + */ +TEST(DDSMonitorServiceTest, monitor_service_late_joiner_consumer_receives_only_the_latest_instance_statuses) +{ +#ifdef FASTDDS_STATISTICS + + // In this test we enforce validating DeadlineMissed samples only + std::bitset validation_mask; + validation_mask[statistics::StatusKind::DEADLINE_MISSED] = true; + + // Setup + MonitorServiceConsumer MSC(validation_mask, false); + MonitorServiceParticipant MSP; + + MSP.setup(); + MSP.enable_monitor_service(); + + DataReaderQos dr_qos; + DataWriterQos dw_qos; + + // Set deadline as 0,5 secs to continously keep missing the deadline later + dr_qos.deadline().period = eprosima::fastdds::dds::Time_t{0, 500000000}; + dw_qos.deadline().period = eprosima::fastdds::dds::Time_t{0, 500000000}; + + MSP.create_and_add_reader(dr_qos); + MSP.create_and_add_writer(dw_qos); + + std::list non_expected_msgs; + MonitorServiceType::type endpoint_deadline_msg; + StatisticsGUIDList r_guids, w_guids; + + // Prepare the expected messages or, in this case, the non-expected one + // since we should never receive a deadline missed status with a total count of 2 + // That will prove that only the last update of the instance is being received and, in turn, + // verify that the monitor service datawriter is not holding past samples of the same instance. + r_guids = MSP.get_reader_guids(); + ASSERT_EQ(r_guids.size(), 1); + endpoint_deadline_msg.local_entity(r_guids.back()); + + statistics::DeadlineMissedStatus_s deadline_missed_status; + deadline_missed_status.total_count() = 2; + endpoint_deadline_msg.value().deadline_missed_status(deadline_missed_status); + + non_expected_msgs.push_back(endpoint_deadline_msg); + + w_guids = MSP.get_writer_guids(); + ASSERT_EQ(w_guids.size(), 1); + endpoint_deadline_msg.local_entity(w_guids.back()); + + non_expected_msgs.push_back(endpoint_deadline_msg); + + auto samples = default_helloworld_data_generator(5); + + //send a sample every 600 milliseconds (more than the deadline period) + MSP.send(samples, 600); + + // Late joiner consumer + MSC.set_reader_history_kind(eprosima::fastdds::dds::KEEP_ALL_HISTORY_QOS) + .init_monitor_service_reader(); + + MSC.start_reception(non_expected_msgs); + + // We expect not to have received any deadline missed status + // with a total_count less than 5 + ASSERT_FALSE(MSC.block_for_all(std::chrono::seconds(5))); +#endif //FASTDDS_STATISTICS +} + +/** + * Test checking that the monitor service properly serializes the optional qos + * for the participant/reader/writer. + */ +TEST(DDSMonitorServiceTest, monitor_service_proxy_optional_qos) +{ +#ifdef FASTDDS_STATISTICS + //! Validate PROXY samples only + std::bitset validation_mask; + validation_mask[statistics::StatusKind::PROXY] = true; + + //! Setup + MonitorServiceParticipant MSP; + MonitorServiceConsumer MSC(validation_mask); + + DomainParticipantQos participant_qos; + + auto test_flow_controller = std::make_shared(); + test_flow_controller->name = "test_flow_controller"; + test_flow_controller->scheduler = FlowControllerSchedulerPolicy::FIFO; + test_flow_controller->max_bytes_per_period = 0; + test_flow_controller->period_ms = static_cast(50); + participant_qos.flow_controllers().push_back(test_flow_controller); + participant_qos.wire_protocol().builtin.flow_controller_name = "test_flow_controller"; + participant_qos.wire_protocol().builtin.mutation_tries = 5; + participant_qos.wire_protocol().builtin.discovery_config.initial_announcements.count = 6; + participant_qos.wire_protocol().builtin.discovery_config.initial_announcements.period = {3, 0}; + participant_qos.wire_protocol().builtin.discovery_config.leaseDuration = {100, 0}; + participant_qos.wire_protocol().builtin.readerPayloadSize = 700; + participant_qos.wire_protocol().builtin.writerPayloadSize = 800; + + Locator_t locator; + locator.kind = LOCATOR_KIND_UDPv4; + locator.port = participant_qos.wire_protocol().port.getUnicastPort((uint32_t)GET_PID() % 230, 1); + IPLocator::setIPv4(locator, "127.0.0.1"); + participant_qos.wire_protocol().builtin.metatrafficUnicastLocatorList.push_back(locator); + + LocatorWithMask locator_with_mask; + IPLocator::setIPv4(locator_with_mask, "127.0.0.1"); + locator_with_mask.port = locator.port; + locator_with_mask.mask(8); + ExternalLocators external_locators; + external_locators[0][1].push_back(locator_with_mask); + participant_qos.wire_protocol().builtin.metatraffic_external_unicast_locators = external_locators; + + locator.port += 1; + participant_qos.wire_protocol().default_unicast_locator_list.push_back(locator); + + locator_with_mask.port = locator.port; + external_locators[0][1].clear(); + external_locators[0][1].push_back(locator_with_mask); + participant_qos.wire_protocol().default_external_unicast_locators = external_locators; + + IPLocator::setIPv4(locator, "239.255.0.1"); + locator.port = participant_qos.wire_protocol().port.getMulticastPort((uint32_t)GET_PID() % 230); + participant_qos.wire_protocol().builtin.metatrafficMulticastLocatorList.push_back(locator); + participant_qos.wire_protocol().builtin.initialPeersList.push_back(locator); + + MSP.setup(participant_qos); + + SampleValidator* validator = MSC.get_sample_validator(); + + //! Assert optional qos when received the proxy datas + validator->set_assert_optional_remote_data(); + + //! Prepare the expected builtin data + //! To be received by the MSC + + ParticipantBuiltinTopicData expected_participant_builtin_topic_data; + PublicationBuiltinTopicData expected_publication_builtin_topic_data; + SubscriptionBuiltinTopicData expected_subscription_builtin_topic_data; + + expected_participant_builtin_topic_data.guid = statistics::to_fastdds_type(MSP.get_participant_guid()); + expected_participant_builtin_topic_data.wire_protocol = participant_qos.wire_protocol(); + expected_participant_builtin_topic_data.wire_protocol->prefix = + expected_participant_builtin_topic_data.guid.guidPrefix; + expected_participant_builtin_topic_data.wire_protocol->participant_id = 0; + expected_participant_builtin_topic_data.wire_protocol->builtin.network_configuration = LOCATOR_KIND_UDPv4; + + validator->register_remote_participant_builtin_topic_data(expected_participant_builtin_topic_data); + + //! Procedure + MSC.init_monitor_service_reader(); + MSP.enable_monitor_service(); + + std::list expected_msgs; + + MonitorServiceType::type participant_proxy_msg, writer_proxy_msg, reader_proxy_msg; + + participant_proxy_msg.status_kind(eprosima::fastdds::statistics::StatusKind::PROXY); + participant_proxy_msg.local_entity(MSP.get_participant_guid()); + + expected_msgs.push_back(participant_proxy_msg); + + DataWriterQos writer_qos; + writer_qos.writer_data_lifecycle().autodispose_unregistered_instances = false; + writer_qos.publish_mode().kind = eprosima::fastdds::dds::ASYNCHRONOUS_PUBLISH_MODE; + writer_qos.publish_mode().flow_controller_name = "test_flow_controller"; + writer_qos.reliable_writer_qos().disable_positive_acks.enabled = true; + writer_qos.reliable_writer_qos().disable_positive_acks.duration = {1, 0}; + writer_qos.writer_resource_limits().matched_subscriber_allocation = 1000; + + MSP.create_and_add_writer(writer_qos); + + writer_proxy_msg.status_kind(eprosima::fastdds::statistics::StatusKind::PROXY); + StatisticsGUIDList guids = MSP.get_writer_guids(); + + ASSERT_EQ(guids.size(), 1u); + writer_proxy_msg.local_entity(guids.back()); + + expected_msgs.push_back(writer_proxy_msg); + + expected_publication_builtin_topic_data.guid = statistics::to_fastdds_type(guids.back()); + expected_publication_builtin_topic_data.writer_data_lifecycle = writer_qos.writer_data_lifecycle(); + expected_publication_builtin_topic_data.publish_mode = writer_qos.publish_mode(); + expected_publication_builtin_topic_data.rtps_reliable_writer = writer_qos.reliable_writer_qos(); + expected_publication_builtin_topic_data.writer_resource_limits = writer_qos.writer_resource_limits(); + + validator->register_remote_publication_builtin_topic_data(expected_publication_builtin_topic_data); + + DataReaderQos reader_qos; + reader_qos.reader_data_lifecycle().autopurge_disposed_samples_delay = {10, 0}; + reader_qos.reader_data_lifecycle().autopurge_no_writer_samples_delay = {5, 0}; + reader_qos.reliable_reader_qos().disable_positive_acks.enabled = true; + reader_qos.reliable_reader_qos().disable_positive_acks.duration = {1, 0}; + reader_qos.reader_resource_limits().matched_publisher_allocation = 500; + reader_qos.reader_resource_limits().outstanding_reads_allocation = 1000; + reader_qos.reader_resource_limits().sample_infos_allocation = 1500; + reader_qos.reader_resource_limits().max_samples_per_read = 200; + + MSP.create_and_add_reader(reader_qos); + + reader_proxy_msg.status_kind(eprosima::fastdds::statistics::StatusKind::PROXY); + guids = MSP.get_reader_guids(); + + ASSERT_EQ(guids.size(), 1u); + reader_proxy_msg.local_entity(guids.back()); + + expected_msgs.push_back(reader_proxy_msg); + + expected_subscription_builtin_topic_data.guid = statistics::to_fastdds_type(guids.back()); + expected_subscription_builtin_topic_data.reader_data_lifecycle = reader_qos.reader_data_lifecycle(); + expected_subscription_builtin_topic_data.rtps_reliable_reader = reader_qos.reliable_reader_qos(); + expected_subscription_builtin_topic_data.reader_resource_limits = reader_qos.reader_resource_limits(); + + validator->register_remote_subscription_builtin_topic_data(expected_subscription_builtin_topic_data); + + MSC.start_reception(expected_msgs); + + //! Assertions + ASSERT_EQ(MSC.block_for_all(std::chrono::seconds(5)), expected_msgs.size()); +#endif //FASTDDS_STATISTICS +} diff --git a/test/blackbox/common/DDSBlackboxTestsOwnershipQos.cpp b/test/blackbox/common/DDSBlackboxTestsOwnershipQos.cpp new file mode 100644 index 00000000000..c09c37a5620 --- /dev/null +++ b/test/blackbox/common/DDSBlackboxTestsOwnershipQos.cpp @@ -0,0 +1,2377 @@ +// Copyright 2022 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include +#include +#include + +#include "BlackboxTests.hpp" +#include "PubSubReader.hpp" +#include "PubSubWriter.hpp" + +using namespace eprosima::fastdds; +using namespace eprosima::fastdds::rtps; + +namespace { +enum communication_type +{ + TRANSPORT, + INTRAPROCESS, + DATASHARING +}; +} // namespace + +class OwnershipQos : public testing::TestWithParam +{ +public: + + void SetUp() override + { + eprosima::fastdds::LibrarySettings library_settings; + switch (GetParam()) + { + case INTRAPROCESS: + library_settings.intraprocess_delivery = eprosima::fastdds::IntraprocessDeliveryType::INTRAPROCESS_FULL; + eprosima::fastdds::dds::DomainParticipantFactory::get_instance()->set_library_settings( + library_settings); + break; + case DATASHARING: + enable_datasharing = true; + break; + case TRANSPORT: + default: + break; + } + } + + void TearDown() override + { + eprosima::fastdds::LibrarySettings library_settings; + switch (GetParam()) + { + case INTRAPROCESS: + library_settings.intraprocess_delivery = eprosima::fastdds::IntraprocessDeliveryType::INTRAPROCESS_OFF; + eprosima::fastdds::dds::DomainParticipantFactory::get_instance()->set_library_settings( + library_settings); + break; + case DATASHARING: + enable_datasharing = false; + break; + case TRANSPORT: + default: + break; + } + } + +}; + +void exclusive_kind_non_keyed_sample_reception( + bool reliable) +{ + PubSubReader reader(TEST_TOPIC_NAME); + PubSubWriter writer1(TEST_TOPIC_NAME); + PubSubWriter writer2(TEST_TOPIC_NAME); + PubSubWriter writer3(TEST_TOPIC_NAME); + + reader.ownership_exclusive().reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + writer1.ownership_strength(1).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + writer2.ownership_strength(2).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + writer3.ownership_strength(3).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + + ASSERT_TRUE(reader.isInitialized()); + ASSERT_TRUE(writer1.isInitialized()); + ASSERT_TRUE(writer2.isInitialized()); + ASSERT_TRUE(writer3.isInitialized()); + + // Wait for discovery. + writer1.wait_discovery(); + writer2.wait_discovery(); + writer3.wait_discovery(); + reader.wait_discovery(std::chrono::seconds(1), 3); + + std::function&)> wait_functor = + [](PubSubWriter&) + { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + }; + + if (reliable) + { + wait_functor = [](PubSubWriter& writer) + { + writer.waitForAllAcked(std::chrono::milliseconds(100)); + }; + } + + auto data = default_helloworld_data_generator(13); + reader.startReception(data); + + decltype(data) denied_samples; + + // DW1 sends a sample. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 sends a sample. + writer2.send_sample(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW1 sends a sample. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 sends a sample. + writer2.send_sample(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW3 sends a sample. + writer3.send_sample(data.front()); + data.pop_front(); + wait_functor(writer3); + + // DW1 sends a sample. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 sends a sample. + writer2.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW3 sends a sample. + writer3.send_sample(data.front()); + data.pop_front(); + wait_functor(writer3); + + // DW2 changes its strength to 4. + ASSERT_TRUE(writer2.ownership_strength(4).set_qos()); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Wait the reader receives the update + + // DW3 sends a sample. + writer3.send_sample(data.front()); + data.pop_front(); + wait_functor(writer3); + + // DW3 changes its strength to 1. + ASSERT_TRUE(writer3.ownership_strength(1).set_qos()); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Wait the reader receives the update + + // DW3 sends a sample. + writer3.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer3); + + // DW1 sends a sample. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 sends a sample. + writer2.send_sample(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW3 sends a sample. + writer3.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer3); + + reader.block_for_at_least(7); + ASSERT_EQ(denied_samples.size(), reader.data_not_received().size()); + ASSERT_EQ(denied_samples, reader.data_not_received()); +} + +/*! + * @test DDS-OWN-SAMPLE-01 Tests samples reception works successfully with Non-Keyed types, Reliable, Ownership QoS + * EXCLUSIVE and dynamic change of strength. + */ +TEST_P(OwnershipQos, exclusive_kind_non_keyed_reliable_sample_reception) +{ + exclusive_kind_non_keyed_sample_reception(true); +} + +void exclusive_kind_keyed_sample_reception( + bool reliable) +{ + PubSubReader reader(TEST_TOPIC_NAME); + PubSubWriter writer1(TEST_TOPIC_NAME); + PubSubWriter writer2(TEST_TOPIC_NAME); + PubSubWriter writer3(TEST_TOPIC_NAME); + PubSubWriter writer4(TEST_TOPIC_NAME); + + reader.ownership_exclusive().reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + writer1.ownership_strength(1).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + writer2.ownership_strength(2).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + writer3.ownership_strength(3).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + writer4.ownership_strength(4).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + + ASSERT_TRUE(reader.isInitialized()); + ASSERT_TRUE(writer1.isInitialized()); + ASSERT_TRUE(writer2.isInitialized()); + ASSERT_TRUE(writer3.isInitialized()); + ASSERT_TRUE(writer4.isInitialized()); + + // Wait for discovery. + writer1.wait_discovery(); + writer2.wait_discovery(); + writer3.wait_discovery(); + writer4.wait_discovery(); + reader.wait_discovery(std::chrono::seconds(1), 4); + + std::function&)> wait_functor = + [](PubSubWriter&) + { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + }; + + if (reliable) + { + wait_functor = [](PubSubWriter& writer) + { + writer.waitForAllAcked(std::chrono::milliseconds(100)); + }; + } + + auto data = default_keyedhelloworld_data_generator(23); + reader.startReception(data); + + decltype(data) denied_samples; + + // DW1 sends a sample of instance 1. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 sends a sample of instance 2. + writer2.send_sample(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW2 sends a sample of instance 1. + writer2.send_sample(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW3 sends a sample of instance 2. + writer3.send_sample(data.front()); + data.pop_front(); + wait_functor(writer3); + + // DW1 sends a sample of instance 1. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW1 sends a sample of instance 2. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 sends a sample of instance 1. + writer2.send_sample(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW2 sends a sample of instance 2. + writer2.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW4 sends a sample of instance 1. + writer4.send_sample(data.front()); + data.pop_front(); + wait_functor(writer4); + + // DW2 sends a sample of instance 2. + writer2.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW1 sends a sample of instance 1. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW1 sends a sample of instance 2. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 sends a sample of instance 1. + writer2.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW2 sends a sample of instance 2. + writer2.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW2 changes its strength to 4. + ASSERT_TRUE(writer2.ownership_strength(4).set_qos()); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Wait the reader receives the update + + // DW1 sends a sample of instance 1. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW3 sends a sample of instance 2. + writer3.send_sample(data.front()); + data.pop_front(); + wait_functor(writer3); + + // DW4 sends a sample of instance 1. + writer4.send_sample(data.front()); + data.pop_front(); + wait_functor(writer4); + + // DW3 changes its strength to 1. + ASSERT_TRUE(writer3.ownership_strength(1).set_qos()); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Wait the reader receives the update + + // DW3 sends a sample of instance 2. + writer3.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer3); + + // DW1 sends a sample of instance 1. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 sends a sample of instance 2. + writer2.send_sample(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW1 sends a sample of instance 1. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW3 sends a sample of instance 2. + writer3.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer3); + + // DW4 sends a sample of instance 1. + writer4.send_sample(data.front()); + data.pop_front(); + wait_functor(writer4); + + reader.block_for_at_least(10); + ASSERT_EQ(denied_samples.size(), reader.data_not_received().size()); + ASSERT_EQ(denied_samples, reader.data_not_received()); +} + +/*! + * @test DDS-OWN-SAMPLE-02 Tests samples reception works successfully with Keyed types, Reliable, Ownership QoS + * EXCLUSIVE and dynamic change of strength. + */ +TEST_P(OwnershipQos, exclusive_kind_keyed_reliable_sample_reception) +{ + exclusive_kind_keyed_sample_reception(true); +} + +/*! + * @test DDS-OWN-SAMPLE-03 Tests samples reception works successfully with Non-Keyed types, BestEffort, Ownership QoS + * EXCLUSIVE and dynamic change of strength. + */ +TEST_P(OwnershipQos, exclusive_kind_non_keyed_besteffort_sample_reception) +{ + exclusive_kind_non_keyed_sample_reception(false); +} + +/*! + * @test DDS-OWN-SAMPLE-04 Tests samples reception works successfully with Keyed types, BestEffort, Ownership QoS + * EXCLUSIVE and dynamic change of strength. + */ +TEST_P(OwnershipQos, exclusive_kind_keyed_besteffort_sample_reception) +{ + exclusive_kind_keyed_sample_reception(false); +} + +void exclusive_kind_non_keyed_writers_same_guid( + bool reliable) +{ + PubSubReader reader(TEST_TOPIC_NAME); + PubSubWriter writer1(TEST_TOPIC_NAME); + PubSubWriter writer2(TEST_TOPIC_NAME); + PubSubWriter writer3(TEST_TOPIC_NAME); + + reader.ownership_exclusive().reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + writer1.ownership_strength(10).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + writer2.ownership_strength(10).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + writer3.ownership_strength(10).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + + ASSERT_TRUE(reader.isInitialized()); + ASSERT_TRUE(writer1.isInitialized()); + ASSERT_TRUE(writer2.isInitialized()); + ASSERT_TRUE(writer3.isInitialized()); + + // Wait for discovery. + writer1.wait_discovery(); + writer2.wait_discovery(); + writer3.wait_discovery(); + reader.wait_discovery(std::chrono::seconds(1), 3); + + std::function&)> wait_functor = + [](PubSubWriter&) + { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + }; + + if (reliable) + { + wait_functor = [](PubSubWriter& writer) + { + writer.waitForAllAcked(std::chrono::milliseconds(100)); + }; + } + + auto data = default_helloworld_data_generator(7); + reader.startReception(data); + + decltype(data) denied_samples; + + // DW2 sends a sample. + writer2.send_sample(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW3 sends a sample. + writer3.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer3); + + // DW2 sends a sample. + writer2.send_sample(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW1 sends a sample. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW3 sends a sample. + writer3.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer3); + + // DW2 sends a sample. + writer2.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW1 sends a sample. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + reader.block_for_at_least(4); + ASSERT_EQ(denied_samples.size(), reader.data_not_received().size()); + ASSERT_EQ(denied_samples, reader.data_not_received()); +} + +/*! + * @test DDS-OWN-SAMPLE-05 Tests samples reception works successfully with Non-Keyed types, Reliable, Ownership QoS + * EXCLUSIVE and several writers with same GUID. + */ +TEST_P(OwnershipQos, exclusive_kind_non_keyed_reliable_writers_same_guid) +{ + exclusive_kind_non_keyed_writers_same_guid(true); +} + +void exclusive_kind_keyed_writers_same_guid( + bool reliable) +{ + PubSubReader reader(TEST_TOPIC_NAME); + PubSubWriter writer1(TEST_TOPIC_NAME); + PubSubWriter writer2(TEST_TOPIC_NAME); + PubSubWriter writer3(TEST_TOPIC_NAME); + PubSubWriter writer4(TEST_TOPIC_NAME); + + reader.ownership_exclusive().reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + writer1.ownership_strength(10).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + writer2.ownership_strength(10).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + writer3.ownership_strength(10).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + writer4.ownership_strength(10).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + + ASSERT_TRUE(reader.isInitialized()); + ASSERT_TRUE(writer1.isInitialized()); + ASSERT_TRUE(writer2.isInitialized()); + ASSERT_TRUE(writer3.isInitialized()); + ASSERT_TRUE(writer4.isInitialized()); + + // Wait for discovery. + writer1.wait_discovery(); + writer2.wait_discovery(); + writer3.wait_discovery(); + writer4.wait_discovery(); + reader.wait_discovery(std::chrono::seconds(1), 4); + + std::function&)> wait_functor = + [](PubSubWriter&) + { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + }; + + if (reliable) + { + wait_functor = [](PubSubWriter& writer) + { + writer.waitForAllAcked(std::chrono::milliseconds(100)); + }; + } + + auto data = default_keyedhelloworld_data_generator(10); + reader.startReception(data); + + decltype(data) denied_samples; + + // DW2 sends a sample of instance 1. + writer2.send_sample(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW3 sends a sample of instance 2. + writer3.send_sample(data.front()); + data.pop_front(); + wait_functor(writer3); + + // DW4 sends a sample of instance 1. + writer4.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer4); + + // DW4 sends a sample of instance 2. + writer4.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer4); + + // DW2 sends a sample of instance 1. + writer2.send_sample(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW3 sends a sample of instance 2. + writer3.send_sample(data.front()); + data.pop_front(); + wait_functor(writer3); + + // DW1 sends a sample of instance 1. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW1 sends a sample of instance 2. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 sends a sample of instance 1. + writer2.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW3 sends a sample of instance 2. + writer2.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer2); + + reader.block_for_at_least(6); + ASSERT_EQ(denied_samples.size(), reader.data_not_received().size()); + ASSERT_EQ(denied_samples, reader.data_not_received()); +} + +/*! + * @test DDS-OWN-SAMPLE-06 Tests samples reception works successfully with Keyed types, Reliable, Ownership QoS + * EXCLUSIVE and several writers with same GUID. + */ +TEST_P(OwnershipQos, exclusive_kind_keyed_reliable_writers_same_guid) +{ + exclusive_kind_keyed_writers_same_guid(true); +} + +/*! + * @test DDS-OWN-SAMPLE-07 Tests samples reception works successfully with Keyed types, BestEffort, Ownership QoS + * EXCLUSIVE and several writers with same GUID. + */ +TEST_P(OwnershipQos, exclusive_kind_non_keyed_besteffort_writers_same_guid) +{ + exclusive_kind_non_keyed_writers_same_guid(false); +} + +/*! + * @test DDS-OWN-SAMPLE-08 Tests samples reception works successfully with Keyed types, BestEffort, Ownership QoS + * EXCLUSIVE and several writers with same GUID. + */ +TEST_P(OwnershipQos, exclusive_kind_keyed_besteffort_writers_same_guid) +{ + exclusive_kind_keyed_writers_same_guid(false); +} + +/*! + * @test DDS-OWN-DEADLINE-01 Tests Ownership changes when the current owner doesn't comply with deadline QoS, in a + * Reliable communication with Non-Keyed types. + */ +TEST_P(OwnershipQos, exclusive_kind_non_keyed_reliable_deadline) +{ + + PubSubReader reader(TEST_TOPIC_NAME); + PubSubWriter writer1(TEST_TOPIC_NAME); + PubSubWriter writer2(TEST_TOPIC_NAME); + + reader.ownership_exclusive().reliability(eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS).deadline_period({0, + 500000000}) + .lease_duration( + {50, 0}, {3, 0}).init(); + writer1.ownership_strength(1).deadline_period({0, 500000000}).lease_duration({50, 0}, {3, 0}).init(); + writer2.ownership_strength(2).deadline_period({0, 500000000}).lease_duration({50, 0}, {3, 0}).init(); + + + ASSERT_TRUE(reader.isInitialized()); + ASSERT_TRUE(writer1.isInitialized()); + ASSERT_TRUE(writer2.isInitialized()); + + // Wait for discovery. + writer1.wait_discovery(); + writer2.wait_discovery(); + reader.wait_discovery(std::chrono::seconds(1), 2); + + auto data = default_helloworld_data_generator(10); + reader.startReception(data); + + decltype(data) denied_samples; + + writer1.send_sample(data.front()); + data.pop_front(); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + writer2.send_sample(data.front()); + data.pop_front(); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + writer2.send_sample(data.front()); + data.pop_front(); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + writer2.send_sample(data.front()); + data.pop_front(); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + writer1.send_sample(data.front()); + data.pop_front(); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + writer1.send_sample(data.front()); + data.pop_front(); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + writer1.send_sample(data.front()); + data.pop_front(); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + reader.block_for_seq({0, 7}); + ASSERT_EQ(denied_samples.size(), reader.data_not_received().size()); + ASSERT_EQ(denied_samples, reader.data_not_received()); +} + +/*! + * @test DDS-OWN-DEADLINE-02 Tests Ownership changes when the current owner doesn't comply with deadline QoS, in a + * Reliable communication with Keyed types. + */ +TEST_P(OwnershipQos, exclusive_kind_keyed_reliable_deadline) +{ + + PubSubReader reader(TEST_TOPIC_NAME); + PubSubWriter writer1(TEST_TOPIC_NAME); + PubSubWriter writer2(TEST_TOPIC_NAME); + + reader.ownership_exclusive().reliability(eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS).deadline_period({0, + 500000000}) + .lease_duration( + {50, 0}, {3, 0}).init(); + writer1.ownership_strength(1).deadline_period({0, 500000000}).lease_duration({50, 0}, {3, 0}).init(); + writer2.ownership_strength(2).deadline_period({0, 500000000}).lease_duration({50, 0}, {3, 0}).init(); + + ASSERT_TRUE(reader.isInitialized()); + ASSERT_TRUE(writer1.isInitialized()); + ASSERT_TRUE(writer2.isInitialized()); + + // Wait for discovery. + writer1.wait_discovery(); + writer2.wait_discovery(); + reader.wait_discovery(std::chrono::seconds(1), 2); + + auto data = default_keyedhelloworld_data_generator(26); + reader.startReception(data); + + decltype(data) denied_samples; + + writer1.send_sample(data.front()); + data.pop_front(); + writer1.send_sample(data.front()); + data.pop_front(); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + writer2.send_sample(data.front()); + data.pop_front(); + writer2.send_sample(data.front()); + data.pop_front(); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + writer2.send_sample(data.front()); + data.pop_front(); + writer2.send_sample(data.front()); + data.pop_front(); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + writer2.send_sample(data.front()); + data.pop_front(); + writer2.send_sample(data.front()); + data.pop_front(); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + writer1.send_sample(data.front()); + data.pop_front(); + writer2.send_sample(data.front()); + data.pop_front(); + denied_samples.push_back(data.front()); + data.pop_front(); + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + writer1.send_sample(data.front()); + data.pop_front(); + writer2.send_sample(data.front()); + data.pop_front(); + denied_samples.push_back(data.front()); + data.pop_front(); + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + writer1.send_sample(data.front()); + data.pop_front(); + writer2.send_sample(data.front()); + data.pop_front(); + denied_samples.push_back(data.front()); + data.pop_front(); + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + + reader.block_for_seq({0, 13}); + ASSERT_EQ(denied_samples.size(), reader.data_not_received().size()); + ASSERT_EQ(denied_samples, reader.data_not_received()); +} + +void exclusive_kind_non_keyed_undiscovered_writer( + bool reliable) +{ + PubSubReader reader(TEST_TOPIC_NAME); + PubSubWriter writer1(TEST_TOPIC_NAME); + PubSubWriter writer2(TEST_TOPIC_NAME); + PubSubWriter writer3(TEST_TOPIC_NAME); + + reader.ownership_exclusive().reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + writer1.ownership_strength(1).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + writer2.ownership_strength(2).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + writer3.ownership_strength(3).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + + ASSERT_TRUE(reader.isInitialized()); + ASSERT_TRUE(writer1.isInitialized()); + ASSERT_TRUE(writer2.isInitialized()); + ASSERT_TRUE(writer3.isInitialized()); + + // Wait for discovery. + writer1.wait_discovery(); + writer2.wait_discovery(); + writer3.wait_discovery(); + reader.wait_discovery(std::chrono::seconds(1), 3); + + std::function&)> wait_functor = + [](PubSubWriter&) + { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + }; + + if (reliable) + { + wait_functor = [](PubSubWriter& writer) + { + writer.waitForAllAcked(std::chrono::milliseconds(100)); + }; + } + + auto data = default_helloworld_data_generator(11); + reader.startReception(data); + + decltype(data) denied_samples; + + // DW1 sends a sample. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 sends a sample. + writer2.send_sample(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW1 sends a sample. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 sends a sample. + writer2.send_sample(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW3 sends a sample. + writer3.send_sample(data.front()); + data.pop_front(); + wait_functor(writer3); + + // DW1 sends a sample. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 sends a sample. + writer2.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW3 sends a sample. + writer3.send_sample(data.front()); + data.pop_front(); + wait_functor(writer3); + + // DW3 is detroyed + writer3.destroy(); + reader.wait_writer_undiscovery(2); + + // DW1 sends a sample. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 sends a sample. + writer2.send_sample(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW1 is detroyed + writer1.destroy(); + reader.wait_writer_undiscovery(1); + + // DW2 sends a sample. + writer2.send_sample(data.front()); + data.pop_front(); + wait_functor(writer2); + + reader.block_for_at_least(7); + ASSERT_EQ(denied_samples.size(), reader.data_not_received().size()); + ASSERT_EQ(denied_samples, reader.data_not_received()); +} + +/*! + * @test DDS-OWN-UNDISC-01 Tests Ownership changes when writer removes himself, in a Reliable communication with + * Non-Keyed types. + */ +TEST_P(OwnershipQos, exclusive_kind_non_keyed_reliable_undiscovered_writer) +{ + exclusive_kind_non_keyed_undiscovered_writer(true); +} + +void exclusive_kind_keyed_undiscovered_writer( + bool reliable) +{ + PubSubReader reader(TEST_TOPIC_NAME); + PubSubWriter writer1(TEST_TOPIC_NAME); + PubSubWriter writer2(TEST_TOPIC_NAME); + PubSubWriter writer3(TEST_TOPIC_NAME); + PubSubWriter writer4(TEST_TOPIC_NAME); + + reader.ownership_exclusive().reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + writer1.ownership_strength(1).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + writer2.ownership_strength(2).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + writer3.ownership_strength(3).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + writer4.ownership_strength(4).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + + ASSERT_TRUE(reader.isInitialized()); + ASSERT_TRUE(writer1.isInitialized()); + ASSERT_TRUE(writer2.isInitialized()); + ASSERT_TRUE(writer3.isInitialized()); + ASSERT_TRUE(writer4.isInitialized()); + + // Wait for discovery. + writer1.wait_discovery(); + writer2.wait_discovery(); + writer3.wait_discovery(); + writer4.wait_discovery(); + reader.wait_discovery(std::chrono::seconds(1), 4); + + std::function&)> wait_functor = + [](PubSubWriter&) + { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + }; + + if (reliable) + { + wait_functor = [](PubSubWriter& writer) + { + writer.waitForAllAcked(std::chrono::milliseconds(100)); + }; + } + + auto data = default_keyedhelloworld_data_generator(24); + reader.startReception(data); + + decltype(data) denied_samples; + + // DW1 sends a sample in instance 1. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 sends a sample in instance 1. + writer2.send_sample(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW3 sends a sample in instance 2. + writer3.send_sample(data.front()); + data.pop_front(); + wait_functor(writer3); + + // DW1 sends a sample in instance 1. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW4 sends a sample in instance 1. + writer4.send_sample(data.front()); + data.pop_front(); + wait_functor(writer4); + + // DW3 sends a sample in instance 2. + writer3.send_sample(data.front()); + data.pop_front(); + wait_functor(writer3); + + // DW1 sends a sample in instance 1. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 sends a sample in instance 1. + writer2.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW3 sends a sample in instance 2. + writer3.send_sample(data.front()); + data.pop_front(); + wait_functor(writer3); + + // DW3 is destroyed. + writer3.destroy(); + reader.wait_writer_undiscovery(3); + + // DW1 sends a sample in instance 1. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 sends a sample in instance 1. + writer2.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW4 sends a sample in instance 1. + writer4.send_sample(data.front()); + data.pop_front(); + wait_functor(writer4); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 is destroyed. + writer2.destroy(); + reader.wait_writer_undiscovery(2); + + // DW1 sends a sample in instance 1. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW4 sends a sample in instance 1. + writer4.send_sample(data.front()); + data.pop_front(); + wait_functor(writer4); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW4 is destroyed. + writer4.destroy(); + reader.wait_writer_undiscovery(1); + + // DW1 sends a sample in instance 1. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + reader.block_for_at_least(16); + ASSERT_EQ(denied_samples.size(), reader.data_not_received().size()); + ASSERT_EQ(denied_samples, reader.data_not_received()); +} + +/*! + * @test DDS-OWN-UNDISC-02 Tests Ownership changes when writer removes himself, in a Reliable communication with + * Keyed types. + */ +TEST_P(OwnershipQos, exclusive_kind_keyed_reliable_undiscovered_writer) +{ + exclusive_kind_keyed_undiscovered_writer(true); +} + +/*! + * @test DDS-OWN-UNDISC-03 Tests Ownership changes when writer removes himself, in a BestEffort communication with + * Non-Keyed types. + */ +TEST_P(OwnershipQos, exclusive_kind_non_keyed_besteffort_undiscovered_writer) +{ + exclusive_kind_non_keyed_undiscovered_writer(false); +} + +/*! + * @test DDS-OWN-UNDISC-04 Tests Ownership changes when writer removes himself, in a BestEffort communication with + * Keyed types. + */ +TEST_P(OwnershipQos, exclusive_kind_keyed_besteffort_undiscovered_writer) +{ + exclusive_kind_keyed_undiscovered_writer(false); +} + +void exclusive_kind_non_keyed_lost_liveliness( + bool reliable) +{ + PubSubReader reader(TEST_TOPIC_NAME); + PubSubWriter writer1(TEST_TOPIC_NAME); + PubSubWriter writer2(TEST_TOPIC_NAME); + PubSubWriter writer3(TEST_TOPIC_NAME); + + std::atomic drop_messages1(false); + auto testTransport1 = std::make_shared(); + testTransport1->messages_filter_ = [&drop_messages1](eprosima::fastdds::rtps::CDRMessage_t&) + { + return drop_messages1.load(); + }; + std::atomic drop_messages3(false); + auto testTransport3 = std::make_shared(); + testTransport3->messages_filter_ = [&drop_messages3](eprosima::fastdds::rtps::CDRMessage_t&) + { + return drop_messages3.load(); + }; + + reader.ownership_exclusive().reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ) + .lease_duration({1, 0}, {0, 500000000}) + .init(); + writer1.ownership_strength(1).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ) + .add_user_transport_to_pparams(testTransport1) + .disable_builtin_transport() + .lease_duration({1, 0}, {0, 500000000}) + .init(); + writer2.ownership_strength(2).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ) + .lease_duration({1, 0}, {0, 500000000}) + .init(); + writer3.ownership_strength(3).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ) + .add_user_transport_to_pparams(testTransport3) + .disable_builtin_transport() + .lease_duration({1, 0}, {0, 500000000}) + .init(); + + ASSERT_TRUE(reader.isInitialized()); + ASSERT_TRUE(writer1.isInitialized()); + ASSERT_TRUE(writer2.isInitialized()); + ASSERT_TRUE(writer3.isInitialized()); + + // Wait for discovery. + writer1.wait_discovery(); + writer2.wait_discovery(); + writer3.wait_discovery(); + reader.wait_discovery(std::chrono::seconds(1), 3); + + std::function&)> wait_functor = + [](PubSubWriter&) + { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + }; + + if (reliable) + { + wait_functor = [](PubSubWriter& writer) + { + writer.waitForAllAcked(std::chrono::milliseconds(100)); + }; + } + + auto data = default_helloworld_data_generator(11); + reader.startReception(data); + + decltype(data) denied_samples; + + // DW1 sends a sample. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 sends a sample. + writer2.send_sample(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW1 sends a sample. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 sends a sample. + writer2.send_sample(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW3 sends a sample. + writer3.send_sample(data.front()); + data.pop_front(); + wait_functor(writer3); + + // DW1 sends a sample. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 sends a sample. + writer2.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW3 sends a sample. + writer3.send_sample(data.front()); + data.pop_front(); + wait_functor(writer3); + + // DW3 liveliness lost. + drop_messages3 = true; + reader.wait_writer_undiscovery(2); + + // DW1 sends a sample. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 sends a sample. + writer2.send_sample(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW1 liveliness lost. + drop_messages1 = true; + reader.wait_writer_undiscovery(1); + + // DW2 sends a sample. + writer2.send_sample(data.front()); + data.pop_front(); + wait_functor(writer2); + + reader.block_for_at_least(7); + ASSERT_EQ(denied_samples.size(), reader.data_not_received().size()); + ASSERT_EQ(denied_samples, reader.data_not_received()); +} + +/*! + * @test DDS-OWN-LIVE-01 Tests Ownership changes when writer removes himself, in a Reliable communication with + * Non-Keyed types. + */ +TEST_P(OwnershipQos, exclusive_kind_non_keyed_reliable_lost_liveliness) +{ + exclusive_kind_non_keyed_lost_liveliness(true); +} + +void exclusive_kind_keyed_lost_liveliness( + bool reliable) +{ + PubSubReader reader(TEST_TOPIC_NAME); + PubSubWriter writer1(TEST_TOPIC_NAME); + PubSubWriter writer2(TEST_TOPIC_NAME); + PubSubWriter writer3(TEST_TOPIC_NAME); + PubSubWriter writer4(TEST_TOPIC_NAME); + + std::atomic drop_messages2(false); + auto testTransport2 = std::make_shared(); + testTransport2->messages_filter_ = [&drop_messages2](eprosima::fastdds::rtps::CDRMessage_t&) + { + return drop_messages2.load(); + }; + std::atomic drop_messages3(false); + auto testTransport3 = std::make_shared(); + testTransport3->messages_filter_ = [&drop_messages3](eprosima::fastdds::rtps::CDRMessage_t&) + { + return drop_messages3.load(); + }; + std::atomic drop_messages4(false); + auto testTransport4 = std::make_shared(); + testTransport4->messages_filter_ = [&drop_messages4](eprosima::fastdds::rtps::CDRMessage_t&) + { + return drop_messages4.load(); + }; + + reader.ownership_exclusive().reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ) + .lease_duration({1, 0}, {0, 500000000}) + .init(); + writer1.ownership_strength(1).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ) + .lease_duration({1, 0}, {0, 500000000}) + .init(); + writer2.ownership_strength(2).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ) + .add_user_transport_to_pparams(testTransport2) + .disable_builtin_transport() + .lease_duration({1, 0}, {0, 500000000}) + .init(); + writer3.ownership_strength(3).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ) + .add_user_transport_to_pparams(testTransport3) + .disable_builtin_transport() + .lease_duration({1, 0}, {0, 500000000}) + .init(); + writer4.ownership_strength(4).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ) + .add_user_transport_to_pparams(testTransport4) + .disable_builtin_transport() + .lease_duration({1, 0}, {0, 500000000}) + .init(); + + ASSERT_TRUE(reader.isInitialized()); + ASSERT_TRUE(writer1.isInitialized()); + ASSERT_TRUE(writer2.isInitialized()); + ASSERT_TRUE(writer3.isInitialized()); + ASSERT_TRUE(writer4.isInitialized()); + + // Wait for discovery. + writer1.wait_discovery(); + writer2.wait_discovery(); + writer3.wait_discovery(); + writer4.wait_discovery(); + reader.wait_discovery(std::chrono::seconds(1), 4); + + std::function&)> wait_functor = + [](PubSubWriter&) + { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + }; + + if (reliable) + { + wait_functor = [](PubSubWriter& writer) + { + writer.waitForAllAcked(std::chrono::milliseconds(100)); + }; + } + + auto data = default_keyedhelloworld_data_generator(24); + reader.startReception(data); + + decltype(data) denied_samples; + + // DW1 sends a sample in instance 1. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 sends a sample in instance 1. + writer2.send_sample(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW3 sends a sample in instance 2. + writer3.send_sample(data.front()); + data.pop_front(); + wait_functor(writer3); + + // DW1 sends a sample in instance 1. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW4 sends a sample in instance 1. + writer4.send_sample(data.front()); + data.pop_front(); + wait_functor(writer4); + + // DW3 sends a sample in instance 2. + writer3.send_sample(data.front()); + data.pop_front(); + wait_functor(writer3); + + // DW1 sends a sample in instance 1. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 sends a sample in instance 1. + writer2.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW3 sends a sample in instance 2. + writer3.send_sample(data.front()); + data.pop_front(); + wait_functor(writer3); + + // DW3 losses liveliness. + drop_messages3 = true; + reader.wait_writer_undiscovery(3); + + // DW1 sends a sample in instance 1. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 sends a sample in instance 1. + writer2.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW4 sends a sample in instance 1. + writer4.send_sample(data.front()); + data.pop_front(); + wait_functor(writer4); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 losses liveliness. + drop_messages2 = true; + reader.wait_writer_undiscovery(2); + + // DW1 sends a sample in instance 1. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW4 sends a sample in instance 1. + writer4.send_sample(data.front()); + data.pop_front(); + wait_functor(writer4); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW4 losses liveliness. + drop_messages4 = true; + reader.wait_writer_undiscovery(1); + + // DW1 sends a sample in instance 1. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + reader.block_for_at_least(16); + ASSERT_EQ(denied_samples.size(), reader.data_not_received().size()); + ASSERT_EQ(denied_samples, reader.data_not_received()); +} + +/*! + * @test DDS-OWN-LIVE-02 Tests Ownership changes when writer removes himself, in a Reliable communication with + * Keyed types. + */ +TEST_P(OwnershipQos, exclusive_kind_keyed_reliable_lost_liveliness) +{ + exclusive_kind_keyed_lost_liveliness(true); +} + +/*! + * @test DDS-OWN-LIVE-03 Tests Ownership changes when writer removes himself, in a BestEffort communication with + * Non-Keyed types. + */ +TEST_P(OwnershipQos, exclusive_kind_non_keyed_besteffort_lost_liveliness) +{ + exclusive_kind_non_keyed_lost_liveliness(false); +} + +/*! + * @test DDS-OWN-LIVE-04 Tests Ownership changes when writer removes himself, in a BestEffort communication with + * Keyed types. + */ +TEST_P(OwnershipQos, exclusive_kind_keyed_besteffort_lost_liveliness) +{ + exclusive_kind_keyed_lost_liveliness(false); +} + +void exclusive_kind_keyed_unregistering_instance( + bool reliable) +{ + PubSubReader reader(TEST_TOPIC_NAME); + PubSubWriter writer1(TEST_TOPIC_NAME); + PubSubWriter writer2(TEST_TOPIC_NAME); + PubSubWriter writer3(TEST_TOPIC_NAME); + PubSubWriter writer4(TEST_TOPIC_NAME); + + reader.ownership_exclusive().reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + writer1.ownership_strength(1).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + writer2.ownership_strength(2).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + writer3.ownership_strength(3).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + writer4.ownership_strength(4).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + + ASSERT_TRUE(reader.isInitialized()); + ASSERT_TRUE(writer1.isInitialized()); + ASSERT_TRUE(writer2.isInitialized()); + ASSERT_TRUE(writer3.isInitialized()); + ASSERT_TRUE(writer4.isInitialized()); + + // Wait for discovery. + writer1.wait_discovery(); + writer2.wait_discovery(); + writer3.wait_discovery(); + writer4.wait_discovery(); + reader.wait_discovery(std::chrono::seconds(1), 4); + + std::function&)> wait_functor = + [](PubSubWriter&) + { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + }; + + if (reliable) + { + wait_functor = [](PubSubWriter& writer) + { + writer.waitForAllAcked(std::chrono::milliseconds(100)); + }; + } + + auto data = default_keyedhelloworld_data_generator(24); + reader.startReception(data); + + decltype(data) denied_samples; + + // DW1 sends a sample in instance 1. + InstanceHandle_t instance_1 = writer1.register_instance(data.front()); + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW1 sends a sample in instance 2. + InstanceHandle_t instance_2 = writer1.register_instance(data.front()); + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 sends a sample in instance 1. + writer2.send_sample(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW3 sends a sample in instance 2. + writer3.send_sample(data.front()); + data.pop_front(); + wait_functor(writer3); + + // DW1 sends a sample in instance 1. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW4 sends a sample in instance 1. + writer4.send_sample(data.front()); + data.pop_front(); + wait_functor(writer4); + + // DW3 sends a sample in instance 2. + writer3.send_sample(data.front()); + data.pop_front(); + wait_functor(writer3); + + // DW1 sends a sample in instance 1. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 sends a sample in instance 1. + writer2.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW3 sends a sample in instance 2. + writer3.send_sample(data.front()); + wait_functor(writer3); + + // DW3 unregisters instance 2. + writer3.unregister_instance(data.front(), instance_2); + data.pop_front(); + + // DW1 sends a sample in instance 1. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 sends a sample in instance 1. + writer2.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW4 sends a sample in instance 1. + writer4.send_sample(data.front()); + data.pop_front(); + wait_functor(writer4); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 unregisters instance 1. + writer2.unregister_instance(data.front(), instance_1); + wait_functor(writer2); + + // DW1 sends a sample in instance 1. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW4 sends a sample in instance 1. + writer4.send_sample(data.front()); + data.pop_front(); + wait_functor(writer4); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW4 is destroyed. + writer4.unregister_instance(data.front(), instance_1); + wait_functor(writer4); + + // DW1 sends a sample in instance 1. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + reader.block_for_at_least(16); + ASSERT_EQ(denied_samples.size(), reader.data_not_received().size()); + ASSERT_EQ(denied_samples, reader.data_not_received()); +} + +/*! + * @test DDS-OWN-UNREG-01 Tests Ownership changes when writer unregisters an instance, in a Reliable communication with + * Keyed types. + */ +TEST_P(OwnershipQos, exclusive_kind_keyed_reliable_unregistering_instance) +{ + exclusive_kind_keyed_unregistering_instance(true); +} + +/*! + * @test DDS-OWN-UNREG-02 Tests Ownership changes when writer unregisters an instance, in a BestEffort communication with + * Keyed types. + */ +TEST_P(OwnershipQos, exclusive_kind_keyed_besteffort_unregistering_instance) +{ + exclusive_kind_keyed_unregistering_instance(false); +} + +void exclusive_kind_keyed_disposing_instance( + bool reliable) +{ + PubSubReader reader(TEST_TOPIC_NAME); + PubSubWriter writer1(TEST_TOPIC_NAME); + PubSubWriter writer2(TEST_TOPIC_NAME); + PubSubWriter writer3(TEST_TOPIC_NAME); + PubSubWriter writer4(TEST_TOPIC_NAME); + + reader.ownership_exclusive().reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + writer1.ownership_strength(1).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + writer2.ownership_strength(2).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + writer3.ownership_strength(3).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + writer4.ownership_strength(4).reliability( + reliable ? eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS : + eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS + ).init(); + + ASSERT_TRUE(reader.isInitialized()); + ASSERT_TRUE(writer1.isInitialized()); + ASSERT_TRUE(writer2.isInitialized()); + ASSERT_TRUE(writer3.isInitialized()); + ASSERT_TRUE(writer4.isInitialized()); + + // Wait for discovery. + writer1.wait_discovery(); + writer2.wait_discovery(); + writer3.wait_discovery(); + writer4.wait_discovery(); + reader.wait_discovery(std::chrono::seconds(1), 4); + + std::function&)> wait_functor = + [](PubSubWriter&) + { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + }; + + if (reliable) + { + wait_functor = [](PubSubWriter& writer) + { + writer.waitForAllAcked(std::chrono::milliseconds(100)); + }; + } + + auto data = default_keyedhelloworld_data_generator(30); + reader.startReception(data); + + decltype(data) denied_samples; + + // DW1 sends a sample in instance 1. + InstanceHandle_t instance_1 = writer1.register_instance(data.front()); + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW1 sends a sample in instance 2. + InstanceHandle_t instance_2 = writer1.register_instance(data.front()); + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 sends a sample in instance 1. + writer2.send_sample(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW3 sends a sample in instance 2. + writer3.send_sample(data.front()); + data.pop_front(); + wait_functor(writer3); + + // DW1 sends a sample in instance 1. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW4 sends a sample in instance 1. + writer4.send_sample(data.front()); + data.pop_front(); + wait_functor(writer4); + + // DW3 sends a sample in instance 2. + writer3.send_sample(data.front()); + data.pop_front(); + wait_functor(writer3); + + // DW1 sends a sample in instance 1. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 sends a sample in instance 1. + writer2.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW3 sends a sample in instance 2. + writer3.send_sample(data.front()); + wait_functor(writer3); + + // DW3 disposes instance 2. + writer3.dispose(data.front(), instance_2); + data.pop_front(); + wait_functor(writer3); + + // DW1 sends a sample in instance 1. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 sends a sample in instance 1. + writer2.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW4 sends a sample in instance 1. + writer4.send_sample(data.front()); + data.pop_front(); + wait_functor(writer4); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 disposes instance 1. + writer2.dispose(data.front(), instance_1); + + // DW1 sends a sample in instance 1. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW4 sends a sample in instance 1. + writer4.send_sample(data.front()); + data.pop_front(); + wait_functor(writer4); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW4 is destroyed. + writer4.dispose(data.front(), instance_1); + wait_functor(writer4); + + // DW1 sends a sample in instance 1. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW2 changes its strength to 5. + ASSERT_TRUE(writer2.ownership_strength(5).set_qos()); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Wait the reader receives the update + // + // DW2 sends a sample in instance 1. + writer2.send_sample(data.front()); + data.pop_front(); + wait_functor(writer2); + + // DW3 sends a sample in instance 2. + writer3.send_sample(data.front()); + data.pop_front(); + wait_functor(writer3); + + // DW1 sends a sample in instance 1. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer1); + + // DW4 sends a sample in instance 1. + writer4.send_sample(data.front()); + denied_samples.push_back(data.front()); + data.pop_front(); + wait_functor(writer4); + + // DW3 disposes instance 2. + writer3.dispose(data.front(), instance_2); + wait_functor(writer3); + + // DW3 is detroyed + writer3.destroy(); + reader.wait_writer_undiscovery(3); + + // DW1 sends a sample in instance 2. + writer1.send_sample(data.front()); + data.pop_front(); + wait_functor(writer1); + + reader.block_for_at_least(12); + ASSERT_EQ(denied_samples.size(), reader.data_not_received().size()); + ASSERT_EQ(denied_samples, reader.data_not_received()); +} + +/*! + * @test DDS-OWN-DISP-01 Tests Ownership changes when writer disposes an instance, in a Reliable communication with + * Keyed types. + */ +TEST_P(OwnershipQos, exclusive_kind_keyed_reliable_disposing_instance) +{ + exclusive_kind_keyed_disposing_instance(true); +} + +/*! + * @test DDS-OWN-DISP-02 Tests Ownership changes when writer disposes an instance, in a BestEffort communication with + * Keyed types. + */ +TEST_P(OwnershipQos, exclusive_kind_keyed_besteffort_disposing_instance) +{ + exclusive_kind_keyed_disposing_instance(false); +} + +/*! + * This is a regression test for redmine issue 20866. + * + * This test checks that a reader keeping a long number of samples and with an exclusive ownership policy only + * returns the data from the writer with the highest strength. + * + * @param use_keep_all_history Whether to use KEEP_ALL history or KEEP_LAST(20). + * @param mixed_data Whether to send data from both writers in an interleaved way. + */ +static void test_exclusive_kind_big_history( + bool use_keep_all_history, + bool mixed_data) +{ + PubSubReader reader(TEST_TOPIC_NAME); + PubSubWriter low_strength_writer(TEST_TOPIC_NAME); + PubSubWriter high_strength_writer(TEST_TOPIC_NAME); + + // Configure history QoS. + if (use_keep_all_history) + { + reader.history_kind(eprosima::fastdds::dds::KEEP_ALL_HISTORY_QOS); + low_strength_writer.history_kind(eprosima::fastdds::dds::KEEP_ALL_HISTORY_QOS); + high_strength_writer.history_kind(eprosima::fastdds::dds::KEEP_ALL_HISTORY_QOS); + } + else + { + reader.history_kind(eprosima::fastdds::dds::KEEP_LAST_HISTORY_QOS).history_depth(20); + low_strength_writer.history_kind(eprosima::fastdds::dds::KEEP_LAST_HISTORY_QOS).history_depth(20); + high_strength_writer.history_kind(eprosima::fastdds::dds::KEEP_LAST_HISTORY_QOS).history_depth(20); + } + + // Prepare data. + std::list generated_data = default_keyedhelloworld_data_generator(20); + auto middle = std::next(generated_data.begin(), 10); + std::list low_strength_data(generated_data.begin(), middle); + std::list high_strength_data(middle, generated_data.end()); + auto expected_data = high_strength_data; + + if (mixed_data) + { + // Expect reception of the first two samples from the low strength writer (one per instance). + auto it = low_strength_data.begin(); + expected_data.push_front(*it++); + expected_data.push_front(*it); + } + + // Initialize writers. + low_strength_writer.ownership_strength(3) + .reliability(eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS) + .init(); + ASSERT_TRUE(low_strength_writer.isInitialized()); + + // High strength writer will use a custom transport to ensure its data is received after the low strength data. + auto test_transport = std::make_shared(); + std::atomic drop_messages(false); + test_transport->messages_filter_ = [&drop_messages](eprosima::fastdds::rtps::CDRMessage_t&) + { + return drop_messages.load(); + }; + high_strength_writer.ownership_strength(4) + .reliability(eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS) + .disable_builtin_transport() + .add_user_transport_to_pparams(test_transport) + .init(); + ASSERT_TRUE(high_strength_writer.isInitialized()); + + // Initialize reader. + reader.ownership_exclusive() + .reliability(eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS) + .init(); + ASSERT_TRUE(reader.isInitialized()); + + // Wait for discovery. + low_strength_writer.wait_discovery(); + high_strength_writer.wait_discovery(); + reader.wait_discovery(std::chrono::seconds::zero(), 2); + + // Drop the messages from the high strength writer, so they arrive later to the reader. + drop_messages.store(true); + + if (mixed_data) + { + // Send one sample from each writer, with low strength data first. + while (!low_strength_data.empty() && !high_strength_data.empty()) + { + EXPECT_TRUE(low_strength_writer.send_sample(low_strength_data.front())); + EXPECT_TRUE(high_strength_writer.send_sample(high_strength_data.front())); + low_strength_data.pop_front(); + high_strength_data.pop_front(); + } + } + else + { + // Send high strength data first, so it has the lowest source timestamps, but drop the messages, so they arrive + // later to the reader. + high_strength_writer.send(high_strength_data); + EXPECT_TRUE(high_strength_data.empty()); + + // Send low strength data, so it has the highest source timestamps. + low_strength_writer.send(low_strength_data); + EXPECT_TRUE(low_strength_data.empty()); + } + + // Wait for the reader to receive the low strength data. + EXPECT_TRUE(low_strength_writer.waitForAllAcked(std::chrono::seconds(1))); + + // Let high strength writer send the data, and wait for the reader to receive it. + drop_messages.store(false); + EXPECT_TRUE(high_strength_writer.waitForAllAcked(std::chrono::seconds(1))); + + // Make the reader process the data, expecting only the required data. + // The issue was reproduced by the reader complaining about reception of unexpected data. + reader.startReception(expected_data); + reader.block_for_all(); +} + +TEST(OwnershipQos, exclusive_kind_keep_all_reliable) +{ + test_exclusive_kind_big_history(true, false); +} + +TEST(OwnershipQos, exclusive_kind_keep_all_reliable_mixed) +{ + test_exclusive_kind_big_history(true, true); +} + +TEST(OwnershipQos, exclusive_kind_keep_last_reliable) +{ + test_exclusive_kind_big_history(false, false); +} + +TEST(OwnershipQos, exclusive_kind_keep_last_reliable_mixed) +{ + test_exclusive_kind_big_history(false, true); +} + +#ifdef INSTANTIATE_TEST_SUITE_P +#define GTEST_INSTANTIATE_TEST_MACRO(x, y, z, w) INSTANTIATE_TEST_SUITE_P(x, y, z, w) +#else +#define GTEST_INSTANTIATE_TEST_MACRO(x, y, z, w) INSTANTIATE_TEST_CASE_P(x, y, z, w) +#endif // ifdef INSTANTIATE_TEST_SUITE_P + +GTEST_INSTANTIATE_TEST_MACRO(OwnershipQos, + OwnershipQos, + testing::Values(TRANSPORT, INTRAPROCESS, DATASHARING), + [](const testing::TestParamInfo& info) + { + switch (info.param) + { + case INTRAPROCESS: + return "Intraprocess"; + break; + case DATASHARING: + return "Datasharing"; + break; + case TRANSPORT: + default: + return "Transport"; + } + }); diff --git a/test/blackbox/common/DDSBlackboxTestsPersistence.cpp b/test/blackbox/common/DDSBlackboxTestsPersistence.cpp index 37374d13db4..99af151c1ae 100644 --- a/test/blackbox/common/DDSBlackboxTestsPersistence.cpp +++ b/test/blackbox/common/DDSBlackboxTestsPersistence.cpp @@ -28,12 +28,14 @@ using namespace eprosima::fastrtps; using namespace eprosima::fastrtps::rtps; +namespace { enum communication_type { TRANSPORT, INTRAPROCESS, DATASHARING }; +} // namespace class PersistenceLargeData : public testing::TestWithParam { diff --git a/test/blackbox/common/DDSBlackboxTestsPersistenceGuid.cpp b/test/blackbox/common/DDSBlackboxTestsPersistenceGuid.cpp index 3e7b5daf751..7830c6978b4 100644 --- a/test/blackbox/common/DDSBlackboxTestsPersistenceGuid.cpp +++ b/test/blackbox/common/DDSBlackboxTestsPersistenceGuid.cpp @@ -28,12 +28,14 @@ using namespace eprosima::fastrtps::rtps; using namespace eprosima::fastdds::dds; +namespace { enum communication_type { TRANSPORT, INTRAPROCESS, DATASHARING }; +} // namespace class PersistenceGuid : public ::testing::TestWithParam { diff --git a/test/blackbox/common/DDSBlackboxTestsPropertyQos.cpp b/test/blackbox/common/DDSBlackboxTestsPropertyQos.cpp new file mode 100644 index 00000000000..5ba2adc7157 --- /dev/null +++ b/test/blackbox/common/DDSBlackboxTestsPropertyQos.cpp @@ -0,0 +1,188 @@ +// Copyright 2021 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include + +#include "BlackboxTests.hpp" +#include "PubSubParticipant.hpp" + +namespace test { +const std::string EXTERNAL_PROPERTY_NAME = "CustomExternalProperty"; +const std::string EXTERNAL_PROPERTY_VALUE = "My Value"; +const std::string INTERNAL_PROPERTY_NAME = "CustomInternalProperty"; +const std::string INTERNAL_PROPERTY_VALUE = "Other Value"; +} // namespace test + + +using namespace eprosima::fastdds; + +namespace { +enum communication_type +{ + TRANSPORT, + INTRAPROCESS, + DATASHARING +}; +} // namespace + +class PropertyQos : public testing::TestWithParam +{ +public: + + void SetUp() override + { + eprosima::fastdds::LibrarySettings library_settings; + switch (GetParam()) + { + case INTRAPROCESS: + library_settings.intraprocess_delivery = eprosima::fastdds::IntraprocessDeliveryType::INTRAPROCESS_FULL; + eprosima::fastdds::dds::DomainParticipantFactory::get_instance()->set_library_settings( + library_settings); + break; + case DATASHARING: + enable_datasharing = true; + break; + case TRANSPORT: + default: + break; + } + } + + void TearDown() override + { + eprosima::fastdds::LibrarySettings library_settings; + switch (GetParam()) + { + case INTRAPROCESS: + library_settings.intraprocess_delivery = eprosima::fastdds::IntraprocessDeliveryType::INTRAPROCESS_OFF; + eprosima::fastdds::dds::DomainParticipantFactory::get_instance()->set_library_settings( + library_settings); + break; + case DATASHARING: + enable_datasharing = false; + break; + case TRANSPORT: + default: + break; + } + } + +}; + +/** + * This test checks that the property qos are correctly sent once the participant is initialized. + * In order to check that the properties are correctly updated, two participants are created and the discovery info is + * checked. It should contain those properties that were meant to be sent, and not those that did not. + */ +TEST_P(PropertyQos, send_property_qos) +{ + // Set Properties that will be sent and those that wont + eprosima::fastdds::rtps::PropertyPolicy source_property_policy; + + // Add external property + { + eprosima::fastdds::rtps::Property property; + property.name(test::EXTERNAL_PROPERTY_NAME); + property.value(test::EXTERNAL_PROPERTY_VALUE); + property.propagate(true); + source_property_policy.properties().push_back(property); + } + // Add internal property + { + eprosima::fastdds::rtps::Property property; + property.name(test::INTERNAL_PROPERTY_NAME); + property.value(test::INTERNAL_PROPERTY_VALUE); + property.propagate(false); + source_property_policy.properties().push_back(property); + } + + PubSubParticipant participant_1(0u, 0u, 0u, 0u); + participant_1.property_policy(source_property_policy); + ASSERT_TRUE(participant_1.init_participant()); + + PubSubParticipant participant_2(0u, 0u, 0u, 0u); + + participant_2.set_on_discovery_function([&](const eprosima::fastdds::dds::ParticipantBuiltinTopicData& info) -> bool + { + std::cout << "Received Property Qos: "; + + // Check the external has arrived and the internal does not + bool property_received = false; + for (auto i : info.properties) + { + std::cout << i.first() << " :{ " << i.second() << " } ; "; + + // Check the internal is not received + if (test::INTERNAL_PROPERTY_NAME == i.first()) + { + return false; + } + + // If it is the external, check the value is correct + if (test::EXTERNAL_PROPERTY_NAME == i.first()) + { + // Avoid double property + if (property_received) + { + return false; + } + else + { + property_received = true; + } + + if (test::EXTERNAL_PROPERTY_VALUE != i.second()) + { + return false; + } + } + } + std::cout << std::endl; + return property_received; + }); + + ASSERT_TRUE(participant_2.init_participant()); + + participant_1.wait_discovery(); + participant_2.wait_discovery_result(); +} + +#ifdef INSTANTIATE_TEST_SUITE_P +#define GTEST_INSTANTIATE_TEST_MACRO(x, y, z, w) INSTANTIATE_TEST_SUITE_P(x, y, z, w) +#else +#define GTEST_INSTANTIATE_TEST_MACRO(x, y, z, w) INSTANTIATE_TEST_CASE_P(x, y, z, w) +#endif // INSTANTIATE_TEST_SUITE_P + +GTEST_INSTANTIATE_TEST_MACRO(PropertyQos, + PropertyQos, + testing::Values(TRANSPORT, INTRAPROCESS, DATASHARING), + [](const testing::TestParamInfo& info) + { + switch (info.param) + { + case INTRAPROCESS: + return "Intraprocess"; + break; + case DATASHARING: + return "Datasharing"; + break; + case TRANSPORT: + default: + return "Transport"; + } + }); diff --git a/test/blackbox/common/DDSBlackboxTestsTransportSHMUDP.cpp b/test/blackbox/common/DDSBlackboxTestsTransportSHMUDP.cpp index c577403e0fb..53fc6c7e075 100644 --- a/test/blackbox/common/DDSBlackboxTestsTransportSHMUDP.cpp +++ b/test/blackbox/common/DDSBlackboxTestsTransportSHMUDP.cpp @@ -36,12 +36,14 @@ using namespace eprosima::fastrtps; using namespace eprosima::fastrtps::rtps; using test_UDPv4Transport = eprosima::fastdds::rtps::test_UDPv4Transport; +namespace { enum communication_type { TRANSPORT, INTRAPROCESS, DATASHARING }; +} // namespace class SHMUDP : public testing::TestWithParam { diff --git a/test/blackbox/common/DDSBlackboxTestsUserDataQos.cpp b/test/blackbox/common/DDSBlackboxTestsUserDataQos.cpp index 3f8c370c03c..4e00c9caa29 100644 --- a/test/blackbox/common/DDSBlackboxTestsUserDataQos.cpp +++ b/test/blackbox/common/DDSBlackboxTestsUserDataQos.cpp @@ -25,12 +25,14 @@ using namespace eprosima::fastrtps; +namespace { enum communication_type { TRANSPORT, INTRAPROCESS, DATASHARING }; +} // namespace class UserDataQos : public testing::TestWithParam { diff --git a/test/blackbox/common/RTPSBlackboxTestsBasic.cpp b/test/blackbox/common/RTPSBlackboxTestsBasic.cpp index 13b5f31a371..a8082328a94 100644 --- a/test/blackbox/common/RTPSBlackboxTestsBasic.cpp +++ b/test/blackbox/common/RTPSBlackboxTestsBasic.cpp @@ -38,11 +38,13 @@ using namespace eprosima::fastrtps; using namespace eprosima::fastrtps::rtps; using test_UDPv4Transport = eprosima::fastdds::rtps::test_UDPv4Transport; +namespace { enum communication_type { TRANSPORT, INTRAPROCESS }; +} // namespace class RTPS : public testing::TestWithParam { diff --git a/test/blackbox/common/RTPSBlackboxTestsDiscovery.cpp b/test/blackbox/common/RTPSBlackboxTestsDiscovery.cpp index 63f372aba13..0a30487d5e3 100644 --- a/test/blackbox/common/RTPSBlackboxTestsDiscovery.cpp +++ b/test/blackbox/common/RTPSBlackboxTestsDiscovery.cpp @@ -24,11 +24,13 @@ using namespace eprosima::fastrtps; using namespace eprosima::fastrtps::rtps; +namespace { enum communication_type { TRANSPORT, INTRAPROCESS }; +} // namespace class RTPSDiscovery : public testing::TestWithParam { diff --git a/test/blackbox/common/RTPSBlackboxTestsPersistence.cpp b/test/blackbox/common/RTPSBlackboxTestsPersistence.cpp index 986964a9e8f..9363801d68a 100644 --- a/test/blackbox/common/RTPSBlackboxTestsPersistence.cpp +++ b/test/blackbox/common/RTPSBlackboxTestsPersistence.cpp @@ -30,11 +30,13 @@ using namespace eprosima::fastrtps; using namespace eprosima::fastrtps::rtps; +namespace { enum communication_type { TRANSPORT, INTRAPROCESS }; +} // namespace class Persistence : public ::testing::TestWithParam { diff --git a/test/blackbox/common/RTPSBlackboxTestsPersistenceGuid.cpp b/test/blackbox/common/RTPSBlackboxTestsPersistenceGuid.cpp index 80e0dd222b7..84cda4570b1 100644 --- a/test/blackbox/common/RTPSBlackboxTestsPersistenceGuid.cpp +++ b/test/blackbox/common/RTPSBlackboxTestsPersistenceGuid.cpp @@ -28,11 +28,13 @@ using namespace eprosima::fastrtps; using namespace eprosima::fastrtps::rtps; +namespace { enum communication_type { TRANSPORT, INTRAPROCESS }; +} // namespace class PersistenceGuid : public ::testing::TestWithParam { diff --git a/test/blackbox/common/RTPSBlackboxTestsPools.cpp b/test/blackbox/common/RTPSBlackboxTestsPools.cpp index c75fb400386..0dd47580704 100644 --- a/test/blackbox/common/RTPSBlackboxTestsPools.cpp +++ b/test/blackbox/common/RTPSBlackboxTestsPools.cpp @@ -26,11 +26,13 @@ using namespace eprosima::fastrtps; using namespace eprosima::fastrtps::rtps; +namespace { enum communication_type { TRANSPORT, INTRAPROCESS }; +} // namespace class RTPSCustomPools : public testing::TestWithParam { diff --git a/test/blackbox/common/RTPSBlackboxTestsReader.cpp b/test/blackbox/common/RTPSBlackboxTestsReader.cpp new file mode 100644 index 00000000000..0695956c544 --- /dev/null +++ b/test/blackbox/common/RTPSBlackboxTestsReader.cpp @@ -0,0 +1,158 @@ +// Copyright 2024 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "BlackboxTests.hpp" + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "RTPSWithRegistrationReader.hpp" +#include "RTPSWithRegistrationWriter.hpp" + +using namespace eprosima::fastdds; +using namespace eprosima::fastdds::rtps; + +namespace { +enum communication_type +{ + TRANSPORT, + INTRAPROCESS +}; +} // namespace + +class RTPSReaderTests : public testing::TestWithParam +{ +public: + + void SetUp() override + { + eprosima::fastdds::LibrarySettings library_settings; + switch (GetParam()) + { + case INTRAPROCESS: + library_settings.intraprocess_delivery = eprosima::fastdds::IntraprocessDeliveryType::INTRAPROCESS_FULL; + eprosima::fastdds::rtps::RTPSDomain::set_library_settings(library_settings); + break; + case TRANSPORT: + default: + break; + } + } + + void TearDown() override + { + eprosima::fastdds::LibrarySettings library_settings; + switch (GetParam()) + { + case INTRAPROCESS: + library_settings.intraprocess_delivery = eprosima::fastdds::IntraprocessDeliveryType::INTRAPROCESS_OFF; + eprosima::fastdds::rtps::RTPSDomain::set_library_settings(library_settings); + break; + case TRANSPORT: + default: + break; + } + } + +}; + +/** + * @test RTPS-READER-API-MWG-01 + * + * matched_writers_guids() must return true with empty list when the entitiy is not matched. + * matched_writers_guids() must return true with a correct list when the entitiy is matched. + */ +TEST_P(RTPSReaderTests, rtpsreader_matched_writers_guids) +{ + RTPSWithRegistrationReader reader(TEST_TOPIC_NAME); + RTPSWithRegistrationWriter writer(TEST_TOPIC_NAME); + + reader.reliability(eprosima::fastdds::rtps::RELIABLE) + .init(); + + writer.reliability(eprosima::fastdds::rtps::BEST_EFFORT) + .init(); + + ASSERT_TRUE(reader.isInitialized()); + ASSERT_TRUE(writer.isInitialized()); + + // Expect not to discover + reader.wait_discovery(std::chrono::seconds(1)); + ASSERT_FALSE(reader.get_matched()); + + std::vector matched_guids; + auto& native_rtps_reader = reader.get_native_reader(); + ASSERT_TRUE(native_rtps_reader.matched_writers_guids(matched_guids)); + ASSERT_TRUE(matched_guids.empty()); + + writer.destroy(); + reader.wait_undiscovery(); + + const size_t num_matched_writers = 3; + std::vector>> writers; + std::vector expected_matched_guids; + + writers.reserve(num_matched_writers); + expected_matched_guids.reserve(num_matched_writers); + + for (size_t i = 0; i < num_matched_writers; ++i) + { + writers.emplace_back(new RTPSWithRegistrationWriter(TEST_TOPIC_NAME)); + writers.back()->init(); + expected_matched_guids.emplace_back(writers.back()->guid()); + } + + reader.wait_discovery(num_matched_writers, std::chrono::seconds::zero()); + ASSERT_EQ(num_matched_writers, reader.get_matched()); + native_rtps_reader.matched_writers_guids(matched_guids); + ASSERT_EQ(expected_matched_guids.size(), matched_guids.size()); + ASSERT_TRUE(std::is_permutation(expected_matched_guids.begin(), expected_matched_guids.end(), + matched_guids.begin())); +} + +#ifdef INSTANTIATE_TEST_SUITE_P +#define GTEST_INSTANTIATE_TEST_MACRO(x, y, z, w) INSTANTIATE_TEST_SUITE_P(x, y, z, w) +#else +#define GTEST_INSTANTIATE_TEST_MACRO(x, y, z, w) INSTANTIATE_TEST_CASE_P(x, y, z, w) +#endif // ifdef INSTANTIATE_TEST_SUITE_P + +GTEST_INSTANTIATE_TEST_MACRO(RTPSReaderTests, + RTPSReaderTests, + testing::Values(TRANSPORT, INTRAPROCESS), + [](const testing::TestParamInfo& info) + { + switch (info.param) + { + case INTRAPROCESS: + return "Intraprocess"; + break; + case TRANSPORT: + default: + return "Transport"; + } + + }); diff --git a/test/blackbox/common/RTPSBlackboxTestsVolatile.cpp b/test/blackbox/common/RTPSBlackboxTestsVolatile.cpp index 7cab100bfa3..7a025f64a65 100644 --- a/test/blackbox/common/RTPSBlackboxTestsVolatile.cpp +++ b/test/blackbox/common/RTPSBlackboxTestsVolatile.cpp @@ -22,11 +22,13 @@ using namespace eprosima::fastrtps; +namespace { enum communication_type { TRANSPORT, INTRAPROCESS }; +} // namespace class Volatile : public testing::TestWithParam { diff --git a/test/blackbox/common/RTPSBlackboxTestsWriter.cpp b/test/blackbox/common/RTPSBlackboxTestsWriter.cpp new file mode 100644 index 00000000000..c67cb7a7e25 --- /dev/null +++ b/test/blackbox/common/RTPSBlackboxTestsWriter.cpp @@ -0,0 +1,158 @@ +// Copyright 2024 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "BlackboxTests.hpp" + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "RTPSWithRegistrationReader.hpp" +#include "RTPSWithRegistrationWriter.hpp" + +using namespace eprosima::fastdds; +using namespace eprosima::fastdds::rtps; + +namespace { +enum communication_type +{ + TRANSPORT, + INTRAPROCESS +}; +} // namespace + +class RTPSWriterTests : public testing::TestWithParam +{ +public: + + void SetUp() override + { + eprosima::fastdds::LibrarySettings library_settings; + switch (GetParam()) + { + case INTRAPROCESS: + library_settings.intraprocess_delivery = eprosima::fastdds::IntraprocessDeliveryType::INTRAPROCESS_FULL; + eprosima::fastdds::rtps::RTPSDomain::set_library_settings(library_settings); + break; + case TRANSPORT: + default: + break; + } + } + + void TearDown() override + { + eprosima::fastdds::LibrarySettings library_settings; + switch (GetParam()) + { + case INTRAPROCESS: + library_settings.intraprocess_delivery = eprosima::fastdds::IntraprocessDeliveryType::INTRAPROCESS_OFF; + eprosima::fastdds::rtps::RTPSDomain::set_library_settings(library_settings); + break; + case TRANSPORT: + default: + break; + } + } + +}; + +/** + * @test RTPS-WRITER-API-MRG-01 + * + * matched_readers_guids() must return true with empty list when the entitiy is not matched. + * matched_readers_guids() must return true with a correct list when the entitiy is matched. + */ +TEST_P(RTPSWriterTests, rtpswriter_matched_readers_guids) +{ + RTPSWithRegistrationWriter writer(TEST_TOPIC_NAME); + RTPSWithRegistrationReader reader(TEST_TOPIC_NAME); + + writer.reliability(eprosima::fastdds::rtps::BEST_EFFORT) + .init(); + + reader.reliability(eprosima::fastdds::rtps::RELIABLE) + .init(); + + ASSERT_TRUE(writer.isInitialized()); + ASSERT_TRUE(reader.isInitialized()); + + // Expect not to discover + writer.wait_discovery(std::chrono::seconds(1)); + ASSERT_FALSE(writer.get_matched()); + + std::vector matched_guids; + auto& native_rtps_writer = writer.get_native_writer(); + ASSERT_TRUE(native_rtps_writer.matched_readers_guids(matched_guids)); + ASSERT_TRUE(matched_guids.empty()); + + reader.destroy(); + writer.wait_undiscovery(); + + const size_t num_matched_readers = 3; + std::vector>> readers; + std::vector expected_matched_guids; + + readers.reserve(num_matched_readers); + expected_matched_guids.reserve(num_matched_readers); + + for (size_t i = 0; i < num_matched_readers; ++i) + { + readers.emplace_back(new RTPSWithRegistrationReader(TEST_TOPIC_NAME)); + readers.back()->init(); + expected_matched_guids.emplace_back(readers.back()->guid()); + } + + writer.wait_discovery(num_matched_readers, std::chrono::seconds::zero()); + ASSERT_EQ(num_matched_readers, writer.get_matched()); + native_rtps_writer.matched_readers_guids(matched_guids); + ASSERT_EQ(expected_matched_guids.size(), matched_guids.size()); + ASSERT_TRUE(std::is_permutation(expected_matched_guids.begin(), expected_matched_guids.end(), + matched_guids.begin())); +} + +#ifdef INSTANTIATE_TEST_SUITE_P +#define GTEST_INSTANTIATE_TEST_MACRO(x, y, z, w) INSTANTIATE_TEST_SUITE_P(x, y, z, w) +#else +#define GTEST_INSTANTIATE_TEST_MACRO(x, y, z, w) INSTANTIATE_TEST_CASE_P(x, y, z, w) +#endif // ifdef INSTANTIATE_TEST_SUITE_P + +GTEST_INSTANTIATE_TEST_MACRO(RTPSWriterTests, + RTPSWriterTests, + testing::Values(TRANSPORT, INTRAPROCESS), + [](const testing::TestParamInfo& info) + { + switch (info.param) + { + case INTRAPROCESS: + return "Intraprocess"; + break; + case TRANSPORT: + default: + return "Transport"; + } + + }); diff --git a/test/mock/rtps/PublisherHistory/fastdds/publisher/DataWriterHistory.hpp b/test/mock/rtps/PublisherHistory/fastdds/publisher/DataWriterHistory.hpp index f851f82c756..244220a02c1 100644 --- a/test/mock/rtps/PublisherHistory/fastdds/publisher/DataWriterHistory.hpp +++ b/test/mock/rtps/PublisherHistory/fastdds/publisher/DataWriterHistory.hpp @@ -77,14 +77,64 @@ class DataWriterHistory : public WriterHistory , resource_limited_qos_(topic_att.resourceLimitsQos) , topic_att_(topic_att) { +<<<<<<< HEAD:test/mock/rtps/PublisherHistory/fastdds/publisher/DataWriterHistory.hpp if (resource_limited_qos_.max_instances == 0) +======= + auto initial_samples = resource_limits_qos.allocated_samples; + auto max_samples = resource_limits_qos.max_samples; + auto extra_samples = resource_limits_qos.extra_samples; + + if (history_qos.kind != KEEP_ALL_HISTORY_QOS) + { + max_samples = history_qos.depth; + if (topic_kind != NO_KEY) + { + if (0 < resource_limits_qos.max_instances) + { + max_samples *= resource_limits_qos.max_instances; + } + else + { + max_samples = -1; + } + } + + initial_samples = std::min(initial_samples, max_samples); + } + + return HistoryAttributes(mempolicy, payloadMaxSize, initial_samples, max_samples, extra_samples); + } + + DataWriterHistory( + const std::shared_ptr& payload_pool, + const std::shared_ptr& change_pool, + const HistoryQosPolicy& history_qos, + const ResourceLimitsQosPolicy& resource_limits_qos, + const rtps::TopicKind_t& topic_kind, + uint32_t payloadMaxSize, + MemoryManagementPolicy_t mempolicy, + std::function unack_sample_remove_functor) + : WriterHistory(to_history_attributes(history_qos, resource_limits_qos, topic_kind, payloadMaxSize, + mempolicy), payload_pool, change_pool) + , history_qos_(history_qos) + , resource_limited_qos_(resource_limits_qos) + , topic_kind_(topic_kind) + , unacknowledged_sample_removed_functor_(unack_sample_remove_functor) + { + if (resource_limited_qos_.max_samples <= 0) + { + resource_limited_qos_.max_samples = -1; + } + + if (resource_limited_qos_.max_instances <= 0) +>>>>>>> 25a43a7c3 (Add UBSan workflow and solve its errors (#6386)):test/mock/dds/DataWriterHistory/fastdds/publisher/DataWriterHistory.hpp { - resource_limited_qos_.max_instances = std::numeric_limits::max(); + resource_limited_qos_.max_instances = -1; } if (resource_limited_qos_.max_samples_per_instance == 0) { - resource_limited_qos_.max_samples_per_instance = std::numeric_limits::max(); + resource_limited_qos_.max_samples_per_instance = -1; } } diff --git a/test/mock/rtps/SharedSecretHandle/rtps/security/common/SharedSecretHandle.h b/test/mock/rtps/SharedSecretHandle/rtps/security/common/SharedSecretHandle.h new file mode 100644 index 00000000000..0c93faabe1f --- /dev/null +++ b/test/mock/rtps/SharedSecretHandle/rtps/security/common/SharedSecretHandle.h @@ -0,0 +1,176 @@ +// Copyright 2026 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/*! + * @file SharedSecretHandle.h (test mock) + * + * Drop-in replacement for the production SharedSecretHandle.h. The only + * difference is the SharedSecretHandle typedef: it instantiates HandleImpl + * with MockAuthenticationPlugin instead of PKIDH, so it matches the dynamic + * type of the SharedSecret-bearing handles created by the test (see + * test/mock/rtps/SecurityPluginFactory/rtps/security/MockAuthenticationPlugin.h). + * + * Without this substitution the production translation unit narrows to a + * different HandleImpl instantiation than the test creates; + * the two share layout but have distinct vtables, so the next virtual call + * (e.g. nil()) trips ubsan with a vptr/type mismatch. + */ +#ifndef _FASTDDS_RTPS_SECURITY_COMMON_SHAREDSECRETHANDLE_H_ +#define _FASTDDS_RTPS_SECURITY_COMMON_SHAREDSECRETHANDLE_H_ + +#include + +#include +#include + +namespace eprosima { +namespace fastdds { +namespace rtps { +namespace security { + +class SharedSecret +{ +public: + + class BinaryData + { + public: + + BinaryData() + { + } + + BinaryData( + const BinaryData& data) + : name_(data.name_) + , value_(data.value_) + { + } + + BinaryData( + BinaryData&& data) + : name_(std::move(data.name_)) + , value_(std::move(data.value_)) + { + } + + BinaryData( + const std::string& name, + const std::vector& value) + : name_(name) + , value_(value) + { + } + + BinaryData( + std::string&& name, + std::vector&& value) + : name_(std::move(name)) + , value_(std::move(value)) + { + } + + BinaryData& operator =( + const BinaryData& data) + { + name_ = data.name_; + value_ = data.value_; + return *this; + } + + BinaryData& operator =( + BinaryData&& data) + { + name_ = std::move(data.name_); + value_ = std::move(data.value_); + return *this; + } + + void name( + const std::string& name) + { + name_ = name; + } + + void name( + std::string&& name) + { + name_ = std::move(name); + } + + const std::string& name() const + { + return name_; + } + + std::string& name() + { + return name_; + } + + void value( + const std::vector& value) + { + value_ = value; + } + + void value( + std::vector&& value) + { + value_ = std::move(value); + } + + const std::vector& value() const + { + return value_; + } + + std::vector& value() + { + return value_; + } + + private: + + std::string name_; + + std::vector value_; + }; + + static const char* const class_id_; + + std::vector data_; +}; + +// Forward declaration is enough. The friend declaration inside HandleImpl +// only requires the elaborated name. +class MockAuthenticationPlugin; + +typedef HandleImpl SharedSecretHandle; + +class SharedSecretHelper +{ +public: + + static const std::vector* find_data_value( + const SecretHandle& sharedsecret, + const std::string& name); +}; + +} //namespace security +} //namespace rtps +} //namespace fastdds +} //namespace eprosima + +#endif // _FASTDDS_RTPS_SECURITY_COMMON_SHAREDSECRETHANDLE_H_ diff --git a/test/mock/rtps/TimedEvent/fastdds/rtps/resources/TimedEvent.h b/test/mock/rtps/TimedEvent/fastdds/rtps/resources/TimedEvent.h index fbd37a98317..fa18e2e0f96 100644 --- a/test/mock/rtps/TimedEvent/fastdds/rtps/resources/TimedEvent.h +++ b/test/mock/rtps/TimedEvent/fastdds/rtps/resources/TimedEvent.h @@ -38,6 +38,13 @@ class TimedEvent { } + TimedEvent( + ResourceEvent&, + std::function, + std::chrono::microseconds) + { + } + MOCK_METHOD0(restart_timer, void()); MOCK_METHOD1(restart_timer, void(const std::chrono::steady_clock::time_point& timeout)); MOCK_METHOD0(cancel_timer, void()); @@ -51,5 +58,9 @@ class TimedEvent } // namespace fastrtps } // namespace eprosima +<<<<<<< HEAD:test/mock/rtps/TimedEvent/fastdds/rtps/resources/TimedEvent.h #endif // _FASTDDS_RTPS_RESOURCES_TIMEDEVENT_H_ +======= +#endif // FASTDDS_RTPS_RESOURCES__TIMEDEVENT_H +>>>>>>> 25a43a7c3 (Add UBSan workflow and solve its errors (#6386)):test/mock/rtps/TimedEvent/rtps/resources/TimedEvent.h diff --git a/test/performance/latency/LatencyTestPublisher.cpp b/test/performance/latency/LatencyTestPublisher.cpp index f78a55bfe3a..d0211391772 100644 --- a/test/performance/latency/LatencyTestPublisher.cpp +++ b/test/performance/latency/LatencyTestPublisher.cpp @@ -937,8 +937,11 @@ void LatencyTestPublisher::analyze_times( stats.received_ = received_count_ - 1; // Because we are not counting the first one. stats.minimum_ = *min_element(times_.begin(), times_.end()); stats.maximum_ = *max_element(times_.begin(), times_.end()); - stats.mean_ = accumulate(times_.begin(), times_.end(), - std::chrono::duration(0)).count() / times_.size(); + if (times_.size() > 0) + { + stats.mean_ = accumulate(times_.begin(), times_.end(), + std::chrono::duration(0)).count() / times_.size(); + } double aux_stdev = 0; for (std::vector>::iterator tit = times_.begin(); tit != times_.end(); @@ -946,7 +949,10 @@ void LatencyTestPublisher::analyze_times( { aux_stdev += pow(((*tit).count() - stats.mean_), 2); } - aux_stdev = sqrt(aux_stdev / times_.size()); + if (times_.size() > 0) + { + aux_stdev = sqrt(aux_stdev / times_.size()); + } stats.stdev_ = aux_stdev; /* Percentiles */ diff --git a/test/unittest/dds/participant/ParticipantTests.cpp b/test/unittest/dds/participant/ParticipantTests.cpp index 89c1279119c..27f0d749c16 100644 --- a/test/unittest/dds/participant/ParticipantTests.cpp +++ b/test/unittest/dds/participant/ParticipantTests.cpp @@ -609,17 +609,23 @@ TEST(ParticipantTests, ChangePSMDomainParticipantQos) } -class DomainParticipantTest : public DomainParticipant -{ -public: +namespace { + +using DomainParticipantImplPtr = DomainParticipantImpl * DomainParticipant::*; - const DomainParticipantImpl* get_impl() const +DomainParticipantImplPtr get_domain_participant_impl_ptr(); + +template +struct DomainParticipantImplAccessor +{ + friend DomainParticipantImplPtr get_domain_participant_impl_ptr() { - return impl_; + return P; } }; +<<<<<<< HEAD void get_rtps_attributes( const DomainParticipant* participant, fastrtps::rtps::RTPSParticipantAttributes& att) @@ -629,6 +635,27 @@ void get_rtps_attributes( const DomainParticipantImpl* participant_impl = participant_test->get_impl(); ASSERT_NE(nullptr, participant_impl); att = participant_impl->rtps_participant()->getRTPSParticipantAttributes(); +======= +template struct DomainParticipantImplAccessor<&DomainParticipant::impl_>; + +} // namespace + +fastdds::rtps::RTPSParticipantAttributes get_rtps_attributes( + const DomainParticipant* participant) +{ + EXPECT_NE(nullptr, participant); + if (participant == nullptr) + { + return {}; + } + const DomainParticipantImpl* participant_impl = participant->*get_domain_participant_impl_ptr(); + EXPECT_NE(nullptr, participant_impl); + if (participant_impl == nullptr) + { + return {}; + } + return participant_impl->get_rtps_participant()->copy_attributes(); +>>>>>>> 25a43a7c3 (Add UBSan workflow and solve its errors (#6386)) } void helper_wait_for_at_least_entries( diff --git a/test/unittest/dds/publisher/DataWriterTests.cpp b/test/unittest/dds/publisher/DataWriterTests.cpp index 6fa031fadc2..c081eac68a7 100644 --- a/test/unittest/dds/publisher/DataWriterTests.cpp +++ b/test/unittest/dds/publisher/DataWriterTests.cpp @@ -1294,28 +1294,48 @@ TEST(DataWriterTests, LoanNegativeTests) ASSERT_TRUE(DomainParticipantFactory::get_instance()->delete_participant(participant) == ReturnCode_t::RETCODE_OK); } -class DataWriterTest : public DataWriter -{ -public: +namespace { + +// Accessor to DataWriterImpl from DataWriter +using DataWriterImplPtr = DataWriterImpl * DataWriter::*; - DataWriterImpl* get_impl() const +DataWriterImplPtr get_datawriter_impl_ptr(); + +template +struct DataWriterImplAccessor +{ + friend DataWriterImplPtr get_datawriter_impl_ptr() { - return impl_; + return P; } }; -class DataWriterImplTest : public DataWriterImpl -{ -public: +template struct DataWriterImplAccessor<&DataWriter::impl_>; + +// Accessor to DataWriterHistory unique ptr from DataWriterImpl +using DataWriterHistoryPtr = std::unique_ptr DataWriterImpl::*; + +DataWriterHistoryPtr get_datawriter_history_ptr(); - DataWriterHistory* get_history() +template +struct DataWriterHistoryAccessor +{ + friend DataWriterHistoryPtr get_datawriter_history_ptr() { +<<<<<<< HEAD return &history_; +======= + return P; +>>>>>>> 25a43a7c3 (Add UBSan workflow and solve its errors (#6386)) } }; +template struct DataWriterHistoryAccessor<&DataWriterImpl::history_>; + +} // namespace + /** * This test checks instance wait_for_acknowledgements API */ @@ -1375,14 +1395,18 @@ TEST(DataWriterTests, InstanceWaitForAcknowledgement) datawriter->get_instance_handle(), max_wait)); #endif // NDEBUG +<<<<<<< HEAD // Access PublisherHistory DataWriterTest* instance_datawriter_test = static_cast(instance_datawriter); ASSERT_NE(nullptr, instance_datawriter_test); DataWriterImpl* datawriter_impl = instance_datawriter_test->get_impl(); +======= + // Access DataWriterHistory + DataWriterImpl* datawriter_impl = instance_datawriter->*get_datawriter_impl_ptr(); +>>>>>>> 25a43a7c3 (Add UBSan workflow and solve its errors (#6386)) ASSERT_NE(nullptr, datawriter_impl); - DataWriterImplTest* datawriter_impl_test = static_cast(datawriter_impl); - ASSERT_NE(nullptr, datawriter_impl_test); - auto history = datawriter_impl_test->get_history(); + DataWriterHistory* history = (datawriter_impl->*get_datawriter_history_ptr()).get(); + ASSERT_NE(nullptr, history); // 5. Calling wait_for_acknowledgments in a keyed topic with c_InstanceHandle_Unknown returns // RETCODE_OK diff --git a/test/unittest/dds/status/ListenerTests.cpp b/test/unittest/dds/status/ListenerTests.cpp index f104734f31a..d2cb5678da2 100644 --- a/test/unittest/dds/status/ListenerTests.cpp +++ b/test/unittest/dds/status/ListenerTests.cpp @@ -37,6 +37,10 @@ #include #include +#include +#include +#include + using ::testing::StrictMock; using ::testing::NiceMock; @@ -107,22 +111,27 @@ class RTPSReaderMock : public eprosima::fastrtps::rtps::RTPSReader }; +<<<<<<< HEAD class RTPSWriterMock : public eprosima::fastrtps::rtps::RTPSWriter +======= +class RTPSWriterMock : public eprosima::fastdds::rtps::BaseWriter +>>>>>>> 25a43a7c3 (Add UBSan workflow and solve its errors (#6386)) { public: - RTPSWriterMock() - { - } + RTPSWriterMock() = default; virtual ~RTPSWriterMock() = default; +<<<<<<< HEAD virtual bool matched_reader_add( const eprosima::fastrtps::rtps::ReaderProxyData&) { return true; } +======= +>>>>>>> 25a43a7c3 (Add UBSan workflow and solve its errors (#6386)) virtual bool matched_reader_remove( const eprosima::fastrtps::rtps::GUID_t&) { @@ -545,6 +554,10 @@ class UserListeners : public ::testing::Test RTPSDomain::writer_ = &writer_mock_; RTPSDomain::reader_ = &reader_mock_; + // BaseWriter::get_participant_impl() must return non-null; some code + // paths (e.g. get_publication_builtin_topic_data) assert on it. + writer_mock_.mp_RTPSParticipant = &participant_impl_mock_; + // Create the DDS entities with the user listeners participant_ = DomainParticipantFactory::get_instance()->create_participant(0, PARTICIPANT_QOS_DEFAULT, @@ -595,6 +608,7 @@ class UserListeners : public ::testing::Test NiceMock participant_mock_; NiceMock writer_mock_; NiceMock reader_mock_; + eprosima::fastdds::rtps::RTPSParticipantImpl participant_impl_mock_; // User listeners are strick, we want to track unexpected calls StrictMock participant_listener_; diff --git a/test/unittest/rtps/discovery/CMakeLists.txt b/test/unittest/rtps/discovery/CMakeLists.txt index c9c9d121c6a..eecec710889 100644 --- a/test/unittest/rtps/discovery/CMakeLists.txt +++ b/test/unittest/rtps/discovery/CMakeLists.txt @@ -84,10 +84,41 @@ target_include_directories(EdpTests PRIVATE target_link_libraries(EdpTests foonathan_memory GTest::gmock ${CMAKE_DL_LIBS}) +<<<<<<< HEAD if(MSVC OR MSVC_IDE) target_link_libraries(EdpTests ${PRIVACY} fastcdr iphlpapi Shlwapi ws2_32) else() target_link_libraries(EdpTests ${PRIVACY} fastcdr) +======= + +gtest_discover_tests(EdpTests) + +#PDP TESTS + +set(TCPTransportInterface_SOURCE + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/messages/CDRMessage.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/messages/RTPSMessageCreator.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/ChannelResource.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/MulticastTransportInterface.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/PortBasedTransportDescriptor.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/tcp/RTCPMessageManager.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/tcp/TCPControlMessage.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TCPAcceptor.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TCPAcceptorBasic.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TCPChannelResource.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TCPChannelResourceBasic.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TCPTransportInterface.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TCPv4Transport.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TCPv6Transport.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TransportInterface.cpp + ) +if(TLS_FOUND) + set(TCPTransportInterface_SOURCE + ${TCPTransportInterface_SOURCE} + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TCPAcceptorSecure.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TCPChannelResourceSecure.cpp + ) +>>>>>>> 25a43a7c3 (Add UBSan workflow and solve its errors (#6386)) endif() add_gtest(EdpTests SOURCES ${EDPTESTS_SOURCE}) diff --git a/test/unittest/rtps/writer/CMakeLists.txt b/test/unittest/rtps/writer/CMakeLists.txt index fdbc373ccc9..90567c673cc 100644 --- a/test/unittest/rtps/writer/CMakeLists.txt +++ b/test/unittest/rtps/writer/CMakeLists.txt @@ -49,6 +49,8 @@ target_include_directories(ReaderProxyTests PRIVATE ${PROJECT_SOURCE_DIR}/test/mock/rtps/ReaderLocator ${PROJECT_SOURCE_DIR}/test/mock/rtps/RTPSGapBuilder ${PROJECT_SOURCE_DIR}/test/mock/rtps/RTPSMessageGroup + ${PROJECT_SOURCE_DIR}/test/mock/rtps/RTPSParticipantImpl + ${PROJECT_SOURCE_DIR}/test/mock/rtps/RTPSReader ${PROJECT_SOURCE_DIR}/test/mock/rtps/TimedEvent ${PROJECT_SOURCE_DIR}/include ${PROJECT_BINARY_DIR}/include diff --git a/test/unittest/security/authentication/CMakeLists.txt b/test/unittest/security/authentication/CMakeLists.txt index 610969b77a8..0cf7eaae013 100644 --- a/test/unittest/security/authentication/CMakeLists.txt +++ b/test/unittest/security/authentication/CMakeLists.txt @@ -26,8 +26,21 @@ set(COMMON_SOURCES_AUTH_PLUGIN_TEST_SOURCE ${PROJECT_SOURCE_DIR}/src/cpp/fastdds/log/StdoutErrConsumer.cpp ${PROJECT_SOURCE_DIR}/src/cpp/rtps/attributes/PropertyPolicy.cpp ${PROJECT_SOURCE_DIR}/src/cpp/rtps/builtin/data/ParticipantProxyData.cpp +<<<<<<< HEAD ${PROJECT_SOURCE_DIR}/src/cpp/fastdds/core/policy/ParameterList.cpp ${PROJECT_SOURCE_DIR}/src/cpp/rtps/flowcontrol/ThroughputControllerDescriptor.cpp +======= + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/common/GuidPrefix_t.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/common/LocatorWithMask.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/common/SerializedPayload.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/common/Time_t.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/common/Token.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/exceptions/Exception.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/flowcontrol/FlowControllerConsts.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/messages/CDRMessage.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/network/utils/netmask_filter.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/network/utils/network.cpp +>>>>>>> 25a43a7c3 (Add UBSan workflow and solve its errors (#6386)) ${PROJECT_SOURCE_DIR}/src/cpp/rtps/resources/ResourceEvent.cpp ${PROJECT_SOURCE_DIR}/src/cpp/rtps/resources/TimedEvent.cpp ${PROJECT_SOURCE_DIR}/src/cpp/rtps/resources/TimedEventImpl.cpp @@ -60,6 +73,8 @@ target_compile_definitions(BuiltinPKIDH PRIVATE target_include_directories(BuiltinPKIDH PRIVATE ${Asio_INCLUDE_DIR} ${PROJECT_SOURCE_DIR}/test/mock/rtps/NetworkFactory/ + ${PROJECT_SOURCE_DIR}/test/mock/rtps/ReaderProxyData/ + ${PROJECT_SOURCE_DIR}/test/mock/rtps/WriterProxyData/ ${OPENSSL_INCLUDE_DIR} ${PROJECT_SOURCE_DIR}/include ${PROJECT_BINARY_DIR}/include ${PROJECT_SOURCE_DIR}/src/cpp diff --git a/test/unittest/security/cryptography/CMakeLists.txt b/test/unittest/security/cryptography/CMakeLists.txt index d921d4e2a68..1cd3f409617 100644 --- a/test/unittest/security/cryptography/CMakeLists.txt +++ b/test/unittest/security/cryptography/CMakeLists.txt @@ -47,6 +47,7 @@ target_compile_definitions(BuiltinAESGCMGMAC PRIVATE target_include_directories(BuiltinAESGCMGMAC PRIVATE ${OPENSSL_INCLUDE_DIR} ${PROJECT_SOURCE_DIR}/test/mock/rtps/SecurityPluginFactory/rtps + ${PROJECT_SOURCE_DIR}/test/mock/rtps/SharedSecretHandle ${PROJECT_SOURCE_DIR}/include ${PROJECT_BINARY_DIR}/include ${PROJECT_SOURCE_DIR}/src/cpp ) diff --git a/test/unittest/statistics/dds/StatisticsDomainParticipantMockTests.cpp b/test/unittest/statistics/dds/StatisticsDomainParticipantMockTests.cpp index 88c0399fa36..8da0382f664 100644 --- a/test/unittest/statistics/dds/StatisticsDomainParticipantMockTests.cpp +++ b/test/unittest/statistics/dds/StatisticsDomainParticipantMockTests.cpp @@ -49,33 +49,62 @@ class StatisticsDomainParticipantMockTests : public ::testing::Test }; -class DomainParticipantImplTest : public DomainParticipantImpl -{ -public: +namespace { + +// Accessor to DomainParticipantImpl* from DomainParticipant +using DomainParticipantImplPtr = + eprosima::fastdds::dds::DomainParticipantImpl * eprosima::fastdds::dds::DomainParticipant::*; - eprosima::fastdds::dds::Publisher* get_builtin_publisher() const +DomainParticipantImplPtr get_domain_participant_impl_ptr(); + +template +struct DomainParticipantImplAccessor +{ + friend DomainParticipantImplPtr get_domain_participant_impl_ptr() { - return builtin_publisher_; + return P; } - PublisherImpl* get_builtin_publisher_impl() const +}; + +template struct DomainParticipantImplAccessor<&eprosima::fastdds::dds::DomainParticipant::impl_>; + +// Accessor to builtin_publisher_ from statistics DomainParticipantImpl +using BuiltinPublisherPtr = eprosima::fastdds::dds::Publisher * DomainParticipantImpl::*; + +BuiltinPublisherPtr get_builtin_publisher_ptr(); + +template +struct BuiltinPublisherAccessor +{ + friend BuiltinPublisherPtr get_builtin_publisher_ptr() { - return builtin_publisher_impl_; + return P; } }; -class DomainParticipantTest : public eprosima::fastdds::dds::DomainParticipant -{ -public: +template struct BuiltinPublisherAccessor<&DomainParticipantImpl::builtin_publisher_>; + +// Accessor to builtin_publisher_impl_ from statistics DomainParticipantImpl +using BuiltinPublisherImplPtr = PublisherImpl * DomainParticipantImpl::*; - eprosima::fastdds::dds::DomainParticipantImpl* get_impl() const +BuiltinPublisherImplPtr get_builtin_publisher_impl_ptr(); + +template +struct BuiltinPublisherImplAccessor +{ + friend BuiltinPublisherImplPtr get_builtin_publisher_impl_ptr() { - return impl_; + return P; } }; +template struct BuiltinPublisherImplAccessor<&DomainParticipantImpl::builtin_publisher_impl_>; + +} // namespace + /** * This test checks that enable_statistics_datawriter fails returning RETCODE_ERROR when create_datawriter fails * returning a nullptr. @@ -107,12 +136,10 @@ TEST_F(StatisticsDomainParticipantMockTests, EnableStatisticsDataWriterFailureCr ASSERT_NE(statistics_participant, nullptr); // 2. Mock create_datawriter - DomainParticipantTest* participant_test = static_cast(participant); - ASSERT_NE(nullptr, participant_test); - DomainParticipantImplTest* statistics_participant_impl_test = static_cast( - participant_test->get_impl()); + DomainParticipantImpl* statistics_participant_impl_test = static_cast( + participant->*get_domain_participant_impl_ptr()); ASSERT_NE(nullptr, statistics_participant_impl_test); - PublisherImpl* builtin_pub_impl = statistics_participant_impl_test->get_builtin_publisher_impl(); + PublisherImpl* builtin_pub_impl = statistics_participant_impl_test->*get_builtin_publisher_impl_ptr(); EXPECT_CALL(*builtin_pub_impl, create_datawriter_mock()).WillOnce(testing::Return(true)); // 3. enable_statistics_datawriter @@ -154,14 +181,12 @@ TEST_F(StatisticsDomainParticipantMockTests, DisableStatisticsDataWriterFailureD ASSERT_NE(statistics_participant, nullptr); // 2. Mock delete_datawriter - DomainParticipantTest* participant_test = static_cast(participant); - ASSERT_NE(nullptr, participant_test); - DomainParticipantImplTest* statistics_participant_impl_test = static_cast( - participant_test->get_impl()); + DomainParticipantImpl* statistics_participant_impl_test = static_cast( + participant->*get_domain_participant_impl_ptr()); ASSERT_NE(nullptr, statistics_participant_impl_test); - eprosima::fastdds::dds::Publisher* builtin_pub = statistics_participant_impl_test->get_builtin_publisher(); + eprosima::fastdds::dds::Publisher* builtin_pub = statistics_participant_impl_test->*get_builtin_publisher_ptr(); ASSERT_NE(nullptr, builtin_pub); - PublisherImpl* builtin_pub_impl = statistics_participant_impl_test->get_builtin_publisher_impl(); + PublisherImpl* builtin_pub_impl = statistics_participant_impl_test->*get_builtin_publisher_impl_ptr(); ASSERT_NE(nullptr, builtin_pub_impl); EXPECT_CALL(*builtin_pub, delete_datawriter_mock()).WillOnce(testing::Return(true)); EXPECT_CALL(*statistics_participant_impl_test, delete_topic_mock()).WillOnce(testing::Return(false)); @@ -209,14 +234,12 @@ TEST_F(StatisticsDomainParticipantMockTests, DisableStatisticsDataWriterFailureD ASSERT_NE(statistics_participant, nullptr); // 2. Mock delete_topic - DomainParticipantTest* participant_test = static_cast(participant); - ASSERT_NE(nullptr, participant_test); - DomainParticipantImplTest* statistics_participant_impl_test = static_cast( - participant_test->get_impl()); + DomainParticipantImpl* statistics_participant_impl_test = static_cast( + participant->*get_domain_participant_impl_ptr()); ASSERT_NE(nullptr, statistics_participant_impl_test); - eprosima::fastdds::dds::Publisher* builtin_pub = statistics_participant_impl_test->get_builtin_publisher(); + eprosima::fastdds::dds::Publisher* builtin_pub = statistics_participant_impl_test->*get_builtin_publisher_ptr(); ASSERT_NE(nullptr, builtin_pub); - PublisherImpl* builtin_pub_impl = statistics_participant_impl_test->get_builtin_publisher_impl(); + PublisherImpl* builtin_pub_impl = statistics_participant_impl_test->*get_builtin_publisher_impl_ptr(); ASSERT_NE(nullptr, builtin_pub_impl); EXPECT_CALL(*statistics_participant_impl_test, delete_topic_mock()).WillOnce(testing::Return(true)); EXPECT_CALL(*builtin_pub, delete_datawriter_mock()).WillOnce(testing::Return(false)); diff --git a/test/unittest/statistics/dds/StatisticsDomainParticipantStatusQueryableTests.cpp b/test/unittest/statistics/dds/StatisticsDomainParticipantStatusQueryableTests.cpp new file mode 100644 index 00000000000..7d66d8f5426 --- /dev/null +++ b/test/unittest/statistics/dds/StatisticsDomainParticipantStatusQueryableTests.cpp @@ -0,0 +1,365 @@ +// Copyright 2023 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../logging/mock/MockConsumer.h" + +constexpr const char* TEST_TOPIC = "test_topic"; + +class TopicDataTypeMock : public eprosima::fastdds::dds::TopicDataType +{ +public: + + TopicDataTypeMock() + : TopicDataType() + { + max_serialized_type_size = 4u; + set_name("footype"); + } + + bool serialize( + const void* const /*data*/, + eprosima::fastdds::rtps::SerializedPayload_t& /*payload*/, + eprosima::fastdds::dds::DataRepresentationId_t /*data_representation*/) override + { + return true; + } + + bool deserialize( + eprosima::fastdds::rtps::SerializedPayload_t& /*payload*/, + void* /*data*/) override + { + return true; + } + + uint32_t calculate_serialized_size( + const void* const /*data*/, + eprosima::fastdds::dds::DataRepresentationId_t /*data_representation*/) override + { + return 0; + } + + void* create_data() override + { + return nullptr; + } + + void delete_data( + void* /*data*/) override + { + } + + bool compute_key( + eprosima::fastdds::rtps::SerializedPayload_t& /*payload*/, + eprosima::fastdds::rtps::InstanceHandle_t& /*ihandle*/, + bool /*force_md5*/) override + { + return true; + } + + bool compute_key( + const void* const /*data*/, + eprosima::fastdds::rtps::InstanceHandle_t& /*ihandle*/, + bool /*force_md5*/) override + { + return true; + } + + void clearName() + { + set_name(""); + } + +private: + + using eprosima::fastdds::dds::TopicDataType::calculate_serialized_size; + using eprosima::fastdds::dds::TopicDataType::serialize; +}; + +namespace eprosima { +namespace fastdds { +namespace statistics { +namespace dds { + +class StatisticsDomainParticipantStatusQueryableTests : public ::testing::Test +{ +public: + + void helper_block_for_at_least_entries( + uint32_t amount) + { + mock_consumer_->wait_for_at_least_entries(amount); + } + + eprosima::fastdds::dds::MockConsumer* mock_consumer_; + +}; + +namespace { + +// Accessor to DomainParticipantImpl* from DomainParticipant +using DomainParticipantImplPtr = + eprosima::fastdds::dds::DomainParticipantImpl * eprosima::fastdds::dds::DomainParticipant::*; + +DomainParticipantImplPtr get_domain_participant_impl_ptr(); + +template +struct DomainParticipantImplAccessor +{ + friend DomainParticipantImplPtr get_domain_participant_impl_ptr() + { + return P; + } + +}; + +template struct DomainParticipantImplAccessor<&eprosima::fastdds::dds::DomainParticipant::impl_>; + +// Accessor to builtin_publisher_impl_ from statistics DomainParticipantImpl +using BuiltinPublisherImplPtr = PublisherImpl * DomainParticipantImpl::*; + +BuiltinPublisherImplPtr get_builtin_publisher_impl_ptr(); + +template +struct BuiltinPublisherImplAccessor +{ + friend BuiltinPublisherImplPtr get_builtin_publisher_impl_ptr() + { + return P; + } + +}; + +template struct BuiltinPublisherImplAccessor<&DomainParticipantImpl::builtin_publisher_impl_>; + +// Accessor to protected get_monitoring_status() member function +using GetMonitoringStatusFn = bool (DomainParticipantImpl::*)( + const fastdds::rtps::GUID_t&, + statistics::MonitorServiceData&); + +GetMonitoringStatusFn get_monitoring_status_fn(); + +template +struct GetMonitoringStatusAccessor +{ + friend GetMonitoringStatusFn get_monitoring_status_fn() + { + return F; + } + +}; + +template struct GetMonitoringStatusAccessor<&DomainParticipantImpl::get_monitoring_status>; + +} // namespace + +TEST_F(StatisticsDomainParticipantStatusQueryableTests, istatus_queryable_get_incompatible_qos) +{ +#ifdef FASTDDS_STATISTICS + // Create DomainParticipant + eprosima::fastdds::dds::DomainParticipant* participant = + eprosima::fastdds::dds::DomainParticipantFactory::get_instance()-> + create_participant(0, eprosima::fastdds::dds::PARTICIPANT_QOS_DEFAULT); + ASSERT_NE(participant, nullptr); + + DomainParticipant* statistics_participant = DomainParticipant::narrow(participant); + ASSERT_NE(statistics_participant, nullptr); + + DomainParticipantImpl* statistics_participant_impl_test = static_cast( + participant->*get_domain_participant_impl_ptr()); + ASSERT_NE(nullptr, statistics_participant_impl_test); + + fastdds::dds::TypeSupport type(new TopicDataTypeMock()); + type.register_type(statistics_participant); + + auto publisher = participant->create_publisher(fastdds::dds::PUBLISHER_QOS_DEFAULT); + auto topic = participant->create_topic(TEST_TOPIC, "footype", fastdds::dds::TOPIC_QOS_DEFAULT); + + //! Create DataWriters + EXPECT_CALL(*publisher, create_datawriter_mock()).WillRepeatedly(testing::Return(false)); + EXPECT_CALL(*statistics_participant_impl_test, delete_topic_mock()).WillRepeatedly(testing::Return(false)); + auto dw1 = publisher->create_datawriter(topic, fastdds::dds::DATAWRITER_QOS_DEFAULT); + auto dw2 = publisher->create_datawriter(topic, fastdds::dds::DATAWRITER_QOS_DEFAULT); + + //! Insert some QoS incompatibilities + auto pub_impl = publisher->get_impl(); + auto statistics_pub_impl = static_cast(pub_impl); + + statistics_pub_impl->insert_policy_violation(dw1->guid(), fastdds::dds::RELIABILITY_QOS_POLICY_ID); + statistics_pub_impl->insert_policy_violation(dw2->guid(), fastdds::dds::RELIABILITY_QOS_POLICY_ID); + + MonitorServiceData incomp_qos_status_dw_1; + incomp_qos_status_dw_1.incompatible_qos_status(IncompatibleQoSStatus_s{}); + MonitorServiceData incomp_qos_status_dw_2; + incomp_qos_status_dw_2.incompatible_qos_status(IncompatibleQoSStatus_s{}); + ASSERT_TRUE((statistics_participant_impl_test->*get_monitoring_status_fn())(dw1->guid(), incomp_qos_status_dw_1)); + ASSERT_TRUE((statistics_participant_impl_test->*get_monitoring_status_fn())(dw2->guid(), incomp_qos_status_dw_2)); + + //! Expect incompatibilities + ASSERT_EQ(1u, incomp_qos_status_dw_1.incompatible_qos_status().total_count()); + ASSERT_EQ(1u, incomp_qos_status_dw_2.incompatible_qos_status().total_count()); + ASSERT_EQ(1u, + incomp_qos_status_dw_1.incompatible_qos_status().policies()[fastdds::dds::RELIABILITY_QOS_POLICY_ID].count()); + ASSERT_EQ(1u, + incomp_qos_status_dw_2.incompatible_qos_status().policies()[fastdds::dds::RELIABILITY_QOS_POLICY_ID].count()); + + statistics_pub_impl->delete_datawriters(); + topic->get_impl()->dereference(); + topic->get_impl()->dereference(); + statistics_participant->delete_topic(topic); + statistics_participant->delete_publisher(publisher); + statistics_participant->delete_contained_entities(); + +#endif // FASTDDS_STATISTICS +} + +TEST_F(StatisticsDomainParticipantStatusQueryableTests, istatus_queryable_get_liveliness_lost_status) +{ +#ifdef FASTDDS_STATISTICS + // Create DomainParticipant + eprosima::fastdds::dds::DomainParticipant* participant = + eprosima::fastdds::dds::DomainParticipantFactory::get_instance()-> + create_participant(0, eprosima::fastdds::dds::PARTICIPANT_QOS_DEFAULT); + ASSERT_NE(participant, nullptr); + + DomainParticipant* statistics_participant = DomainParticipant::narrow(participant); + ASSERT_NE(statistics_participant, nullptr); + + DomainParticipantImpl* statistics_participant_impl_test = static_cast( + participant->*get_domain_participant_impl_ptr()); + ASSERT_NE(nullptr, statistics_participant_impl_test); + + fastdds::dds::TypeSupport type(new TopicDataTypeMock()); + type.register_type(statistics_participant); + + auto publisher = participant->create_publisher(fastdds::dds::PUBLISHER_QOS_DEFAULT); + auto topic = participant->create_topic(TEST_TOPIC, "footype", fastdds::dds::TOPIC_QOS_DEFAULT); + + //! Create DataWriters + EXPECT_CALL(*publisher, create_datawriter_mock()).WillRepeatedly(testing::Return(false)); + EXPECT_CALL(*statistics_participant_impl_test, delete_topic_mock()).WillRepeatedly(testing::Return(false)); + auto dw1 = publisher->create_datawriter(topic, fastdds::dds::DATAWRITER_QOS_DEFAULT); + auto dw2 = publisher->create_datawriter(topic, fastdds::dds::DATAWRITER_QOS_DEFAULT); + + //! Insert some QoS incompatibilities + auto pub_impl = publisher->get_impl(); + auto statistics_pub_impl = static_cast(pub_impl); + + statistics_pub_impl->insert_policy_violation(dw1->guid(), fastdds::dds::LIVELINESS_QOS_POLICY_ID); + statistics_pub_impl->insert_policy_violation(dw2->guid(), fastdds::dds::LIVELINESS_QOS_POLICY_ID); + + MonitorServiceData liv_lost_status_dw_1; + liv_lost_status_dw_1.liveliness_lost_status(LivelinessLostStatus_s{}); + MonitorServiceData liv_lost_status_dw_2; + liv_lost_status_dw_2.liveliness_lost_status(LivelinessLostStatus_s{}); + ASSERT_TRUE((statistics_participant_impl_test->*get_monitoring_status_fn())(dw1->guid(), liv_lost_status_dw_1)); + ASSERT_TRUE((statistics_participant_impl_test->*get_monitoring_status_fn())(dw2->guid(), liv_lost_status_dw_2)); + + //! Expect incompatibilities + ASSERT_EQ(1u, liv_lost_status_dw_1.liveliness_lost_status().total_count()); + ASSERT_EQ(1u, liv_lost_status_dw_2.liveliness_lost_status().total_count()); + + statistics_pub_impl->delete_datawriters(); + topic->get_impl()->dereference(); + topic->get_impl()->dereference(); + statistics_participant->delete_topic(topic); + statistics_participant->delete_publisher(publisher); + statistics_participant->delete_contained_entities(); + +#endif // FASTDDS_STATISTICS +} + +TEST_F(StatisticsDomainParticipantStatusQueryableTests, istatus_queryable_get_deadline_missed_status) +{ +#ifdef FASTDDS_STATISTICS + // Create DomainParticipant + eprosima::fastdds::dds::DomainParticipant* participant = + eprosima::fastdds::dds::DomainParticipantFactory::get_instance()-> + create_participant(0, eprosima::fastdds::dds::PARTICIPANT_QOS_DEFAULT); + ASSERT_NE(participant, nullptr); + + DomainParticipant* statistics_participant = DomainParticipant::narrow(participant); + ASSERT_NE(statistics_participant, nullptr); + + DomainParticipantImpl* statistics_participant_impl_test = static_cast( + participant->*get_domain_participant_impl_ptr()); + ASSERT_NE(nullptr, statistics_participant_impl_test); + + fastdds::dds::TypeSupport type(new TopicDataTypeMock()); + type.register_type(statistics_participant); + + auto publisher = participant->create_publisher(fastdds::dds::PUBLISHER_QOS_DEFAULT); + auto topic = participant->create_topic(TEST_TOPIC, "footype", fastdds::dds::TOPIC_QOS_DEFAULT); + + //! Create DataWriters + EXPECT_CALL(*publisher, create_datawriter_mock()).WillRepeatedly(testing::Return(false)); + EXPECT_CALL(*statistics_participant_impl_test, delete_topic_mock()).WillRepeatedly(testing::Return(false)); + auto dw1 = publisher->create_datawriter(topic, fastdds::dds::DATAWRITER_QOS_DEFAULT); + auto dw2 = publisher->create_datawriter(topic, fastdds::dds::DATAWRITER_QOS_DEFAULT); + + //! Insert some QoS incompatibilities + auto pub_impl = publisher->get_impl(); + auto statistics_pub_impl = static_cast(pub_impl); + + statistics_pub_impl->insert_policy_violation(dw1->guid(), fastdds::dds::DEADLINE_QOS_POLICY_ID); + statistics_pub_impl->insert_policy_violation(dw2->guid(), fastdds::dds::DEADLINE_QOS_POLICY_ID); + + MonitorServiceData deadline_missed_status_dw_1; + deadline_missed_status_dw_1.deadline_missed_status(DeadlineMissedStatus_s{}); + MonitorServiceData deadline_missed_status_dw_2; + deadline_missed_status_dw_2.deadline_missed_status(DeadlineMissedStatus_s{}); + ASSERT_TRUE((statistics_participant_impl_test->*get_monitoring_status_fn())(dw1->guid(), + deadline_missed_status_dw_1)); + ASSERT_TRUE((statistics_participant_impl_test->*get_monitoring_status_fn())(dw2->guid(), + deadline_missed_status_dw_2)); + + //! Expect incompatibilities + ASSERT_EQ(1u, deadline_missed_status_dw_1.deadline_missed_status().total_count()); + ASSERT_EQ(1u, deadline_missed_status_dw_2.deadline_missed_status().total_count()); + + statistics_pub_impl->delete_datawriters(); + topic->get_impl()->dereference(); + topic->get_impl()->dereference(); + statistics_participant->delete_topic(topic); + statistics_participant->delete_publisher(publisher); + statistics_participant->delete_contained_entities(); + +#endif // FASTDDS_STATISTICS +} + +} // dds +} // statistics +} // fastdds +} // eprosima + +int main( + int argc, + char** argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/test/unittest/statistics/dds/StatisticsQosTests.cpp b/test/unittest/statistics/dds/StatisticsQosTests.cpp index c36de74b9ce..0c016609120 100644 --- a/test/unittest/statistics/dds/StatisticsQosTests.cpp +++ b/test/unittest/statistics/dds/StatisticsQosTests.cpp @@ -28,6 +28,97 @@ namespace fastdds { namespace statistics { namespace dds { +<<<<<<< HEAD +======= +#ifdef FASTDDS_STATISTICS +namespace { + +// Accessor to DomainParticipantImpl* from DomainParticipant +using DomainParticipantImplPtr = efd::DomainParticipantImpl * efd::DomainParticipant::*; + +DomainParticipantImplPtr get_domain_participant_impl_ptr(); + +template +struct DomainParticipantImplAccessor +{ + friend DomainParticipantImplPtr get_domain_participant_impl_ptr() + { + return P; + } + +}; + +template struct DomainParticipantImplAccessor<&efd::DomainParticipant::impl_>; + +// Accessor to builtin_publisher_ from statistics DomainParticipantImpl +using BuiltinPublisherPtr = + efd::Publisher * eprosima::fastdds::statistics::dds::DomainParticipantImpl::*; + +BuiltinPublisherPtr get_builtin_publisher_ptr(); + +template +struct BuiltinPublisherAccessor +{ + friend BuiltinPublisherPtr get_builtin_publisher_ptr() + { + return P; + } + +}; + +template struct BuiltinPublisherAccessor< + & eprosima::fastdds::statistics::dds::DomainParticipantImpl::builtin_publisher_>; + +} // namespace +#endif // ifdef FASTDDS_STATISTICS + +class StatisticsFromXMLProfileTests : public ::testing::Test +{ +public: + +#ifdef FASTDDS_STATISTICS + eprosima::fastdds::statistics::dds::DomainParticipant* statistics_participant = nullptr; + eprosima::fastdds::dds::Publisher* statistics_publisher = nullptr; + + void test_setup_XMLConfigurationForStatisticsDataWritersQoS( + eprosima::fastdds::statistics::dds::DomainParticipant*& _statistics_participant, + eprosima::fastdds::dds::Publisher*& statistics_pub, + const std::string& xml) + { + + eprosima::fastdds::dds::DomainParticipantFactory::get_instance()->load_XML_profiles_string(xml.c_str(), + xml.size()); + + // Create DomainParticipant. Builtin statistics DataWriters are also created, as auto-enable option is true by default. + eprosima::fastdds::dds::DomainParticipant* participant = + eprosima::fastdds::dds::DomainParticipantFactory::get_instance()-> + create_participant(0, eprosima::fastdds::dds::PARTICIPANT_QOS_DEFAULT); + ASSERT_NE(participant, nullptr); + + // Obtain pointer to child class + _statistics_participant = + eprosima::fastdds::statistics::dds::DomainParticipant::narrow(participant); + ASSERT_NE(_statistics_participant, nullptr); + + // Get DomainParticipantImpl via accessor (no UB downcast) + efd::DomainParticipantImpl* base_impl = + _statistics_participant->*get_domain_participant_impl_ptr(); + ASSERT_NE(base_impl, nullptr); + + eprosima::fastdds::statistics::dds::DomainParticipantImpl* domain_statistics_participant_impl = + static_cast(base_impl); + ASSERT_NE(domain_statistics_participant_impl, nullptr); + + // Get Publisher via accessor + statistics_pub = + domain_statistics_participant_impl->*get_builtin_publisher_ptr(); + ASSERT_NE(statistics_pub, nullptr); + } + +#endif // ifdef FASTDDS_STATISTICS +}; + +>>>>>>> 25a43a7c3 (Add UBSan workflow and solve its errors (#6386)) /* * This test checks that STATISTICS_DATAWRITER_QOS correctly sets the expected QoS. * 1. Reliability RELIABLE diff --git a/test/unittest/statistics/rtps/CMakeLists.txt b/test/unittest/statistics/rtps/CMakeLists.txt index 9c05f57c2a9..26c9bcc437b 100644 --- a/test/unittest/statistics/rtps/CMakeLists.txt +++ b/test/unittest/statistics/rtps/CMakeLists.txt @@ -37,5 +37,139 @@ target_include_directories(RTPSStatisticsTests PRIVATE ${Asio_INCLUDE_DIR} ) +<<<<<<< HEAD target_link_libraries(RTPSStatisticsTests fastrtps fastcdr GTest::gtest GTest::gmock) add_gtest(RTPSStatisticsTests SOURCES ${STATISTICS_RTPS_TESTS_SOURCE}) +======= +target_link_libraries(RTPSStatisticsTests + fastdds + fastcdr + GTest::gtest + GTest::gmock + $<$:socket> +) +gtest_discover_tests(RTPSStatisticsTests) + +set(TCPTransportInterface_SOURCE + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/messages/RTPSMessageCreator.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/ChannelResource.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/MulticastTransportInterface.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/PortBasedTransportDescriptor.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/tcp/RTCPMessageManager.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/tcp/TCPControlMessage.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TCPAcceptor.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TCPAcceptorBasic.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TCPChannelResource.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TCPChannelResourceBasic.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TCPTransportInterface.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TCPv4Transport.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TCPv6Transport.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TransportInterface.cpp + ) +if(TLS_FOUND) + set(TCPTransportInterface_SOURCE + ${TCPTransportInterface_SOURCE} + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TCPAcceptorSecure.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TCPChannelResourceSecure.cpp + ) +endif() + +set(STATISTICS_RTPS_MONITORSERVICETESTS_SOURCE + MonitorServiceTests.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/fastdds/core/policy/ParameterList.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/fastdds/core/Time_t.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/fastdds/publisher/qos/WriterQos.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/fastdds/subscriber/qos/ReaderQos.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/attributes/PropertyPolicy.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/attributes/ThreadSettings.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/builtin/data/PublicationBuiltinTopicData.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/builtin/data/WriterProxyData.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/common/GuidPrefix_t.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/common/LocatorWithMask.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/common/SerializedPayload.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/common/Time_t.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/flowcontrol/FlowControllerConsts.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/history/CacheChangePool.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/history/TopicPayloadPool.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/history/TopicPayloadPoolRegistry.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/messages/CDRMessage.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/network/NetworkBuffer.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/network/NetworkConfiguration.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/network/NetworkFactory.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/network/utils/netmask_filter.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/network/utils/network.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/resources/ResourceEvent.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/resources/TimedEvent.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/resources/TimedEventImpl.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/ChainingTransport.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/network/NetmaskFilterKind.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/network/NetworkInterface.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/network/NetworkInterfaceWithFilter.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/writer/LocatorSelectorSender.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/statistics/rtps/monitor-service/MonitorService.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/statistics/rtps/monitor-service/MonitorServiceListener.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/statistics/types/monitorservice_typesPubSubTypes.cxx + ${PROJECT_SOURCE_DIR}/src/cpp/statistics/types/monitorservice_typesTypeObjectSupport.cxx + ${PROJECT_SOURCE_DIR}/src/cpp/statistics/types/typesPubSubTypes.cxx + ${PROJECT_SOURCE_DIR}/src/cpp/statistics/types/typesTypeObjectSupport.cxx + ${PROJECT_SOURCE_DIR}/src/cpp/utils/Host.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/utils/IPFinder.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/utils/IPLocator.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/utils/md5.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/utils/SystemInfo.cpp + ${TCPTransportInterface_SOURCE} + ) + +add_executable(MonitorServiceTests ${STATISTICS_RTPS_MONITORSERVICETESTS_SOURCE}) + +target_compile_definitions(MonitorServiceTests PRIVATE FASTDDS_NO_LIB + BOOST_ASIO_STANDALONE + ASIO_STANDALONE + $<$>,$>:__DEBUG> + $<$:__INTERNAL_DEBUG> # Internal debug activated. + ) + +target_include_directories(MonitorServiceTests PRIVATE + mock/Publisher + mock/StatisticsBase + ${PROJECT_SOURCE_DIR}/test/mock/dds/DomainParticipantFactory + ${PROJECT_SOURCE_DIR}/test/mock/rtps/BuiltinProtocols + ${PROJECT_SOURCE_DIR}/test/mock/rtps/EDP + ${PROJECT_SOURCE_DIR}/test/mock/rtps/Endpoint + ${PROJECT_SOURCE_DIR}/test/mock/rtps/external_locators + ${PROJECT_SOURCE_DIR}/test/mock/rtps/ExternalLocatorsProcessor + ${PROJECT_SOURCE_DIR}/test/mock/rtps/ParticipantProxyData + ${PROJECT_SOURCE_DIR}/test/mock/rtps/ReaderHistory + ${PROJECT_SOURCE_DIR}/test/mock/rtps/ReceiverResource + ${PROJECT_SOURCE_DIR}/test/mock/rtps/RTPSDomainImpl + ${PROJECT_SOURCE_DIR}/test/mock/rtps/RTPSParticipantImpl + ${PROJECT_SOURCE_DIR}/test/mock/rtps/RTPSReader + ${PROJECT_SOURCE_DIR}/test/mock/rtps/RTPSWriter + ${PROJECT_SOURCE_DIR}/test/mock/rtps/SecurityManager + ${PROJECT_SOURCE_DIR}/test/mock/rtps/StatefulReader + ${PROJECT_SOURCE_DIR}/test/mock/rtps/StatefulWriter + ${PROJECT_SOURCE_DIR}/test/mock/rtps/StatelessReader + ${PROJECT_SOURCE_DIR}/test/mock/rtps/StatelessWriter + ${PROJECT_SOURCE_DIR}/test/mock/rtps/TypeLookupManager + ${PROJECT_SOURCE_DIR}/test/mock/rtps/WLP + ${PROJECT_SOURCE_DIR}/test/mock/rtps/WriterHistory + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_BINARY_DIR}/include + ${PROJECT_SOURCE_DIR}/src/cpp + ${PROJECT_SOURCE_DIR}/thirdparty/taocpp-pegtl + ${Asio_INCLUDE_DIR} + ) + +target_link_libraries(MonitorServiceTests + fastcdr + fastdds::log + fastdds::xtypes::dynamic-types::impl + fastdds::xtypes::type-representation + GTest::gtest + GTest::gmock + $<$:OpenSSL::SSL$OpenSSL::Crypto> + $<$:socket> + ) + +gtest_discover_tests(MonitorServiceTests) +>>>>>>> 25a43a7c3 (Add UBSan workflow and solve its errors (#6386)) diff --git a/test/unittest/statistics/rtps/RTPSStatisticsTests.cpp b/test/unittest/statistics/rtps/RTPSStatisticsTests.cpp index b80389ccbb3..28ad915ebc0 100644 --- a/test/unittest/statistics/rtps/RTPSStatisticsTests.cpp +++ b/test/unittest/statistics/rtps/RTPSStatisticsTests.cpp @@ -50,6 +50,31 @@ namespace fastdds { namespace statistics { namespace rtps { +<<<<<<< HEAD +======= +namespace { + +// Accessor to RTPSParticipantImpl* from RTPSParticipant +using RTPSParticipantImplPtr = fastdds::rtps::RTPSParticipantImpl * fastdds::rtps::RTPSParticipant::*; + +RTPSParticipantImplPtr get_rtps_part_impl(); + +template +struct RTPSParticipantImplAccessor +{ + friend RTPSParticipantImplPtr get_rtps_part_impl() + { + return P; + } + +}; + +template struct RTPSParticipantImplAccessor<&fastdds::rtps::RTPSParticipant::mp_impl>; + +} // namespace + + +>>>>>>> 25a43a7c3 (Add UBSan workflow and solve its errors (#6386)) struct MockListener : IListener { MockListener() @@ -1155,6 +1180,46 @@ TEST_F(RTPSStatisticsTests, statistics_rpts_unordered_datagrams) EXPECT_EQ(0, last_lost_data.byte_magnitude_order()); } +<<<<<<< HEAD +======= +TEST_F(RTPSStatisticsTests, iconnections_queryable_get_entity_connections) +{ + ConnectionList conns_reader, conns_writer; + create_endpoints(1024); + + // match writer and reader on a dummy topic + match_endpoints(false, "string", "test_topic_name"); + + eprosima::fastdds::rtps::RTPSParticipantImpl* part_impl = participant_->*get_rtps_part_impl(); + + part_impl->get_entity_connections(reader_->getGuid(), conns_reader); + part_impl->get_entity_connections(writer_->getGuid(), conns_writer); + + ASSERT_EQ(1, conns_writer.size()); + ASSERT_EQ(1, conns_writer.size()); + ASSERT_EQ(conns_reader[0].guid(), statistics::to_statistics_type(writer_->getGuid())); + ASSERT_EQ(conns_writer[0].guid(), statistics::to_statistics_type(reader_->getGuid())); + + for (auto& locator : conns_reader[0].announced_locators()) + { + bool found = false; + auto base_writer = static_cast(writer_); + for (auto& writer_loc : base_writer->get_general_locator_selector().locator_selector) + { + //! Checking the address can be confusing since the writer_loc could be translated to + //! 127.0.0.1 + if (statistics::to_statistics_type(writer_loc).port() == locator.port() && + statistics::to_statistics_type(writer_loc).kind() == locator.kind()) + { + found = true; + } + } + + ASSERT_TRUE(found); + } +} + +>>>>>>> 25a43a7c3 (Add UBSan workflow and solve its errors (#6386)) } // namespace rtps } // namespace statistics } // namespace fastdds diff --git a/test/unittest/statistics/rtps/mock/StatisticsBase/statistics/rtps/monitor-service/MonitorService.hpp b/test/unittest/statistics/rtps/mock/StatisticsBase/statistics/rtps/monitor-service/MonitorService.hpp new file mode 100644 index 00000000000..c2f954def9c --- /dev/null +++ b/test/unittest/statistics/rtps/mock/StatisticsBase/statistics/rtps/monitor-service/MonitorService.hpp @@ -0,0 +1,304 @@ +// Copyright 2023 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @file MonitorService.hpp + */ + +#ifndef _STATISTICS_RTPS_MONITOR_SERVICE_MONITORSERVICE_HPP_ +#define _STATISTICS_RTPS_MONITOR_SERVICE_MONITORSERVICE_HPP_ + + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace eprosima { +namespace fastdds { + +namespace rtps { + +struct TopicDescription; + +} // namespace rtps + +namespace statistics { +namespace rtps { + +#ifdef FASTDDS_STATISTICS + +class MonitorService +{ + static constexpr int MIN_TIME_BETWEEN_PUBS_MS = 500; + + friend class MonitorServiceListener; + +public: + + using endpoint_creator_t = std::function; + + using endpoint_registrator_t = std::function; + + MonitorService( + const fastdds::rtps::GUID_t& guid, + IProxyQueryable* proxy_q, + IConnectionsQueryable* conns_q, + IStatusQueryable& status_q, + endpoint_creator_t endpoint_creator, + endpoint_registrator_t endpoint_registrator, + fastdds::rtps::ResourceEvent& event_service); + + void set_writer( + fastdds::rtps::RTPSWriter* writer) + { + status_writer_ = writer; + } + + ~MonitorService(); + + /** + * @brief Enables the Monitor Service in a Participant. + * + * @return Whether the service could be correctly enabled. + * Returns false if the service was already enabled. + */ + bool enable_monitor_service(); + + /** + * @brief Disables the Monitor Service in a Participant. + * + * @return Whether the service could be correctly enabled. + * Returns false if the service was already disabled. + */ + bool disable_monitor_service(); + + /** + * @brief Checks whether the Monitor Service is enabled + * or not. + * + * @return True if the service is enabled. + */ + inline bool is_enabled() + { + return enabled_; + } + + /** + * @brief Checks whether the Monitor Service is spinning callbacks + * or not. + * + * @return True if the service is currently processing. + */ + inline bool is_processing() + { + return timer_active_.load(); + } + + /** + * @brief Getter for the Monitor Service Listener. + * + * @return Pointer to the MonitorServiceListener instance. + */ + inline const MonitorServiceListener* get_listener() + { + return listener_; + } + + /** + * @brief Removes a local previously known entity + * from the guid collection. + * + * @param entity_id Identifier of the entity. + * + * @return True if the operation succeeds. + */ + bool remove_local_entity( + const fastdds::rtps::EntityId_t& entity_id); + + /** + * @brief Adds a new entity status update to the queue + * + * @param entity_id Identifier of the entity. + * @param status_id Identifier of the status. + * + * The status_id can be [PROXY, CONNECTION_LIST, INCOMPATIBLE_QOS, + * INCONSISTENT_TOPIC, LIVELINESS_LOST, LIVELINESS_CHANGED, + * DEADLINE_MISSED, SAMPLE_LOST_STATUS] + * + * @return True if the operation succeeds. + */ + bool push_entity_update( + const fastdds::rtps::EntityId_t& entity_id, + const uint32_t& status_id); + + /** + * @brief Process any updates regarding + * remote entities incompatible QoS matching. + * + * @param local_guid The GUID_t identifying the local entity + * @param remote_guid The GUID_t identifying the remote entity + * @param incompatible_qos The PolicyMask with the incompatible QoS + * + */ + void on_incompatible_qos_matching( + const fastdds::rtps::GUID_t& local_guid, + const fastdds::rtps::GUID_t& remote_guid, + const fastdds::dds::PolicyMask& incompatible_qos_policies); + + /** + * @brief Notifies that a remote proxy data has been removed. + * This is interesting to notify proxy removals independently + * of the remote entity being matched or not. + * + * @param removed_proxy_guid GUID of the removed proxy. + */ + void on_remote_proxy_data_removed( + const fastdds::rtps::GUID_t& removed_proxy_guid); + +private: + + /** + * @brief This operation publishes all the enabled statuses + * of the input entity_id. + * + * @return True if the operation succeeds. + */ + bool write_status( + const fastdds::rtps::EntityId_t& entity_id, + const std::bitset& changed_statuses, + const bool& entity_disposed); + + /** + * @brief Adds a new change to writer history + * + * @return True if the operation succeeds. + */ + bool add_change( + MonitorServiceStatusData& status_data, + const bool& disposed); + + /** + * @brief Creates and initializes the Monitor Service + * Status writer + * + * @return True if the operation succeeds. + */ + bool create_endpoint(); + + /** + * @brief Method that is timerly called and + * proccesses the next element in the queue. + * + * @return True if the operation succeeds. + */ + bool spin_queue(); + + /** + * @brief Initialized a new local entity + * and requests the proxy and connection_list update + * + * @param entity_id Identifier of the entity. + * + * @return true if the entity was correctly initialized + */ + bool initialize_entity( + const fastdds::rtps::EntityId_t& entity_id); + + /** + * @brief Frees the Payload Pool + * + */ + void release_payload_pool(); + + std::atomic enabled_; + + std::atomic initialized_; + + std::atomic timer_active_; + + const fastdds::rtps::GUID_t local_participant_guid_; + + IProxyQueryable* proxy_queryable_; + + IConnectionsQueryable* conns_queryable_; + + IStatusQueryable& status_queryable_; + + //! Stores the local entities. + //! For each entitiy, a bitset indicating the + //! status id that needs to be updated, alongside + //! with a bool that prevents the entity_id from being + //! inserted twice. + std::map, bool>> local_entities_; + + std::unique_ptr event_; + + std::vector changed_entities_; + + std::mutex mtx_; + + MonitorServiceListener* listener_; + + fastdds::rtps::RTPSWriter* status_writer_; + + std::unique_ptr status_writer_history_; + + std::shared_ptr status_writer_payload_pool_; + + endpoint_creator_t endpoint_creator_; + + endpoint_registrator_t endpoint_registrator_; + + MonitorServiceStatusDataPubSubType type_; + + std::map + extended_incompatible_qos_collection_; + + std::mutex extended_incompatible_qos_mtx_; +}; + +#endif // FASTDDS_STATISTICS + +} // namespace rtps +} // namespace statistics +} // namespace fastdds +} // namespace eprosima + +#endif // _STATISTICS_RTPS_MONITOR_SERVICE_MONITORSERVICE_HPP_ diff --git a/test/unittest/transport/CMakeLists.txt b/test/unittest/transport/CMakeLists.txt index 66aae99ee35..2f6505df03f 100644 --- a/test/unittest/transport/CMakeLists.txt +++ b/test/unittest/transport/CMakeLists.txt @@ -55,6 +55,8 @@ set(TCPTransportInterface_SOURCE ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TCPChannelResource.cpp ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TCPChannelResourceBasic.cpp ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TCPTransportInterface.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TCPv4Transport.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TCPv6Transport.cpp ) if(TLS_FOUND) set(TCPTransportInterface_SOURCE @@ -168,6 +170,7 @@ set(TCPV6TESTS_SOURCE ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TCPChannelResource.cpp ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TCPChannelResourceBasic.cpp ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TCPTransportInterface.cpp + ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TCPv4Transport.cpp ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/TCPv6Transport.cpp ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/tcp/RTCPMessageManager.cpp ${PROJECT_SOURCE_DIR}/src/cpp/rtps/transport/tcp/TCPControlMessage.cpp diff --git a/test/unittest/transport/TCPv4Tests.cpp b/test/unittest/transport/TCPv4Tests.cpp index 2a2fceff480..3ded69d6266 100644 --- a/test/unittest/transport/TCPv4Tests.cpp +++ b/test/unittest/transport/TCPv4Tests.cpp @@ -1311,7 +1311,12 @@ TEST_F(TCPv4Tests, secure_non_blocking_send) auto sender_unbound_channel_resources = senderTransportUnderTest.get_unbound_channel_resources(); ASSERT_TRUE(sender_unbound_channel_resources.size() == 1); auto sender_channel_resource = +<<<<<<< HEAD std::static_pointer_cast(sender_unbound_channel_resources[0]); +======= + std::static_pointer_cast( + sender_unbound_channel_resources[0]); +>>>>>>> 25a43a7c3 (Add UBSan workflow and solve its errors (#6386)) // Prepare the message asio::error_code ec;