From 8f878abe58ef93d0397bf14fc334197e1a50d9c6 Mon Sep 17 00:00:00 2001 From: Geoffrey Biggs Date: Sat, 8 Apr 2023 13:35:15 +0900 Subject: [PATCH] [rcl] Improve handling of dynamic discovery (#1023) * Get discovery preferences from the environment Signed-off-by: Geoffrey Biggs * Support specification of discovery range and static peers Signed-off-by: Geoffrey Biggs * Add some debug helpers Signed-off-by: Geoffrey Biggs * Cleanup the LOCALHOST_ONLY deprecation a bit. Only print out the warning if it is actually specified. Signed-off-by: Chris Lalancette * Rewrite parsing of static peers. Mostly to get rid of the use of strtok_r, which is dangerous and also should not be used on our static environment variables. Instead, use rcutils_split(), which is much better. Signed-off-by: Chris Lalancette * Fix some silly bugs. Signed-off-by: Chris Lalancette * Use names instead of integers for discovery range env vars Signed-off-by: Michael X. Grey * Add support for dynamic allocation This commit adds support for dynamic allocation for unlimited number of static peers. Signed-off-by: Arjo Chakravarty * Add a warning if ROS_AUTOMATIC_DISCOVERY_OFF is set and STATIC_PEERS are set. Signed-off-by: Arjo Chakravarty * Update to latest rmw API Signed-off-by: Michael X. Grey * Uncrustify Signed-off-by: Michael X. Grey * Update for rmw_discovery_options_t changes Signed-off-by: Shane Loretz * Address feedback: use strncmp Signed-off-by: Arjo Chakravarty * Address feedback: Log levels Signed-off-by: Arjo Chakravarty * Address feedback: remove TODO Signed-off-by: Arjo Chakravarty * Address feedback: rename function Signed-off-by: Arjo Chakravarty * ddress feedback: Docstring Signed-off-by: Arjo Chakravarty * Address feedback: comment Signed-off-by: Arjo Chakravarty * Add `RCL_RET_ERROR` if fini fails. Signed-off-by: Arjo Chakravarty * `snprintf`->`rcutils_snprintf` Signed-off-by: Arjo Chakravarty * Rename tests Signed-off-by: Arjo Chakravarty * rcl_get_discovery_automatic_range -> rcl_get_automatic_discovery_range in test Signed-off-by: Shane Loretz * Annotate tests Signed-off-by: Arjo Chakravarty * More comments Signed-off-by: Arjo Chakravarty * Style Signed-off-by: Arjo Chakravarty * Style Signed-off-by: Arjo Chakravarty * Uncrustify Signed-off-by: Arjo Chakravarty * Constness and warning Signed-off-by: Arjo Chakravarty * address TODO about IP address validation in the static peers Signed-off-by: William Woodall * NOT_SET and SYSTEM_DEFAULT values Signed-off-by: Shane Loretz * refactor discovery options to handle env vars better and simplify stringifying enums Signed-off-by: William Woodall * fixup docs Signed-off-by: William Woodall * (re)improve the discovery range debug message Signed-off-by: William Woodall * Set discovery options to NOT_SET to detect user changse Signed-off-by: Shane Loretz * lint Signed-off-by: Shane Loretz * strncpy_s on windows Signed-off-by: Shane Loretz * Change default range to SUBNET, and allow configuring it at build time Signed-off-by: Shane Loretz --------- Signed-off-by: Geoffrey Biggs Signed-off-by: Chris Lalancette Signed-off-by: Michael X. Grey Signed-off-by: Arjo Chakravarty Signed-off-by: Shane Loretz Signed-off-by: William Woodall Co-authored-by: Chris Lalancette Co-authored-by: Michael X. Grey Co-authored-by: Arjo Chakravarty Co-authored-by: Shane Loretz Co-authored-by: William Woodall --- rcl/CMakeLists.txt | 7 + rcl/include/rcl/discovery_options.h | 109 +++++++++++ rcl/src/rcl/discovery_options.c | 182 ++++++++++++++++++ rcl/src/rcl/init.c | 74 +++++++- rcl/src/rcl/init_options.c | 6 + rcl/src/rcl/localhost.c | 11 +- rcl/test/CMakeLists.txt | 6 + rcl/test/rcl/test_discovery_options.cpp | 243 ++++++++++++++++++++++++ 8 files changed, 633 insertions(+), 5 deletions(-) create mode 100644 rcl/include/rcl/discovery_options.h create mode 100644 rcl/src/rcl/discovery_options.c create mode 100644 rcl/test/rcl/test_discovery_options.cpp diff --git a/rcl/CMakeLists.txt b/rcl/CMakeLists.txt index ef217f437..03b2ab378 100644 --- a/rcl/CMakeLists.txt +++ b/rcl/CMakeLists.txt @@ -41,6 +41,7 @@ set(${PROJECT_NAME}_sources src/rcl/client.c src/rcl/common.c src/rcl/context.c + src/rcl/discovery_options.c src/rcl/domain_id.c src/rcl/event.c src/rcl/expand_topic_name.c @@ -100,6 +101,12 @@ target_link_libraries(${PROJECT_NAME} PRIVATE yaml ) +# Allow configuring the default discovery range +if(DEFINED RCL_DEFAULT_DISCOVERY_RANGE) +target_compile_definitions(${PROJECT_NAME} PRIVATE + "RCL_DEFAULT_DISCOVERY_RANGE=${RCL_DEFAULT_DISCOVERY_RANGE}") +endif() + # Causes the visibility macros to use dllexport rather than dllimport, # which is appropriate when building the dll but not consuming it. target_compile_definitions(${PROJECT_NAME} PRIVATE "RCL_BUILDING_DLL") diff --git a/rcl/include/rcl/discovery_options.h b/rcl/include/rcl/discovery_options.h new file mode 100644 index 000000000..a62708eea --- /dev/null +++ b/rcl/include/rcl/discovery_options.h @@ -0,0 +1,109 @@ +// Copyright 2022 Open Source Robotics Foundation, Inc. +// +// 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 + +#ifndef RCL__DISCOVERY_OPTIONS_H_ +#define RCL__DISCOVERY_OPTIONS_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "rcl/types.h" +#include "rcl/visibility_control.h" + +#include "rcutils/allocator.h" + +#include "rmw/discovery_options.h" + +/// Determine how the user wishes to discover other ROS nodes automatically. +/** + * Use the ROS_AUTOMATIC_DISCOVERY_RANGE environment variable to determine how + * far automatic discovery should be allowed to propagate: + * + * - not at all (no automatic discovery), + * - the local machine only, + * - or the subnet (or as far beyond the local system as the middleware can) + * + * When the subnet is specified the automatic discovery mechanism used by the + * rmw implementation dictates how far discovery can propagate on the network, + * e.g. for multicast-based discovery this will be the local subnet, hence the + * name. + * + * The option indicated by the environment variable will be stored in the + * automatic_discovery_range field of the given discovery_options struct. + * + * If the environment variable isn't set, then the default discovery range + * will be the value of the preprocessor definition + * `RCL_DEFAULT_DISCOVERY_RANGE`. + * If the definition is undefined, then the default will be SUBNET. + * It is intended that the default will be LOCALHOST in future versions of ROS. + * + * \param[out] discovery_options Must not be NULL. + * \return #RCL_RET_INVALID_ARGUMENT if an argument is invalid, or + * \return #RCL_RET_ERROR if an unexpected error happened, or + * \return #RCL_RET_OK. + */ +RCL_PUBLIC +rcl_ret_t +rcl_get_automatic_discovery_range(rmw_discovery_options_t * discovery_options); + +/// Convert the automatic discovery range value to a string for easy printing. +/** + * \param[in] automatic_discovery_range range enum to stringify + * \return string version of enum, or NULL if not recognized + */ +RCL_PUBLIC +const char * +rcl_automatic_discovery_range_to_string(rmw_automatic_discovery_range_t automatic_discovery_range); + +/// Determine how the user wishes to discover other ROS nodes via statically-configured peers. +/** + * Use the ROS_STATIC_PEERS environment variable to determine which hosts + * the user wants to communicate with, in addition to localhost. + * + * Values for the static peers are not validated beyond basic string checks, + * e.g. avoiding empty strings, etc. + * Any validation of IP addresses or hostnames is left up to the + * rmw implementation, and therefore what is and is not acceptable in these + * fields is dependent on it. + * + * The general expectation, however, is that IP addresses and hostnames are + * acceptable. + * + * The static peers are split by ';' and returned as a list of fixed length + * c-strings in the static_peers and static_peers_count fields of the given + * discovery_options struct. + * Each peer may only be RMW_DISCOVERY_OPTIONS_STATIC_PEERS_MAX_LENGTH long, + * and if it is longer it will be skipped and a warning log message will be + * produced. + * + * \param[out] discovery_options Must not be NULL. + * \return #RCL_RET_INVALID_ARGUMENT if an argument is invalid, or + * \return #RCL_RET_ERROR if an unexpected error happened, or + * \return #RCL_RET_OK. + */ +RCL_PUBLIC +rcl_ret_t +rcl_get_discovery_static_peers( + rmw_discovery_options_t * discovery_options, + rcutils_allocator_t * allocator); + +#ifdef __cplusplus +} +#endif + +#endif // RCL__DISCOVERY_OPTIONS_H_ diff --git a/rcl/src/rcl/discovery_options.c b/rcl/src/rcl/discovery_options.c new file mode 100644 index 000000000..3f10312d5 --- /dev/null +++ b/rcl/src/rcl/discovery_options.c @@ -0,0 +1,182 @@ +// Copyright 2022 Open Source Robotics Foundation, Inc. +// +// 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 "rcl/discovery_options.h" + +#include +#include + +#include "rcutils/allocator.h" +#include "rcutils/env.h" +#include "rcutils/logging_macros.h" +#include "rcutils/snprintf.h" +#include "rcutils/split.h" +#include "rcutils/types/string_array.h" + +#include "rcl/error_handling.h" +#include "rcl/types.h" + +#include "rmw/error_handling.h" + +#include "./common.h" + +static const char * const RCL_STATIC_PEERS_ENV_VAR = "ROS_STATIC_PEERS"; +static const char * const RCL_AUTOMATIC_DISCOVERY_RANGE_ENV_VAR = "ROS_AUTOMATIC_DISCOVERY_RANGE"; + +#define GET_RMW_DISCOVERY_RANGE(x) \ + _GET_DEFAULT_DISCOVERY_RANGE(x) +#define _GET_DEFAULT_DISCOVERY_RANGE(x) \ + RMW_AUTOMATIC_DISCOVERY_RANGE_ ## x + +rcl_ret_t +rcl_get_automatic_discovery_range(rmw_discovery_options_t * discovery_options) +{ + const char * ros_automatic_discovery_range_env_val = NULL; + const char * get_env_error_str = NULL; + + RCUTILS_CAN_SET_MSG_AND_RETURN_WITH_ERROR_OF(RCL_RET_INVALID_ARGUMENT); + RCUTILS_CAN_SET_MSG_AND_RETURN_WITH_ERROR_OF(RCL_RET_ERROR); + RCL_CHECK_ARGUMENT_FOR_NULL(discovery_options, RCL_RET_INVALID_ARGUMENT); + + get_env_error_str = rcutils_get_env( + RCL_AUTOMATIC_DISCOVERY_RANGE_ENV_VAR, + &ros_automatic_discovery_range_env_val); + if (NULL != get_env_error_str) { + RCL_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Error getting env var '%s': %s", RCL_AUTOMATIC_DISCOVERY_RANGE_ENV_VAR, + get_env_error_str); + return RCL_RET_ERROR; + } + if (strcmp(ros_automatic_discovery_range_env_val, "") == 0) { +#ifdef RCL_DEFAULT_DISCOVERY_RANGE + discovery_options->automatic_discovery_range = + GET_RMW_DISCOVERY_RANGE(RCL_DEFAULT_DISCOVERY_RANGE); +#else + discovery_options->automatic_discovery_range = RMW_AUTOMATIC_DISCOVERY_RANGE_SUBNET; +#endif + } else if (strcmp(ros_automatic_discovery_range_env_val, "OFF") == 0) { + discovery_options->automatic_discovery_range = RMW_AUTOMATIC_DISCOVERY_RANGE_OFF; + } else if (strcmp(ros_automatic_discovery_range_env_val, "LOCALHOST") == 0) { + discovery_options->automatic_discovery_range = RMW_AUTOMATIC_DISCOVERY_RANGE_LOCALHOST; + } else if (strcmp(ros_automatic_discovery_range_env_val, "SUBNET") == 0) { + discovery_options->automatic_discovery_range = RMW_AUTOMATIC_DISCOVERY_RANGE_SUBNET; + } else if (strcmp(ros_automatic_discovery_range_env_val, "SYSTEM_DEFAULT") == 0) { + discovery_options->automatic_discovery_range = RMW_AUTOMATIC_DISCOVERY_RANGE_SYSTEM_DEFAULT; + } else { + RCUTILS_LOG_WARN_NAMED( + ROS_PACKAGE_NAME, + "Invalid value '%s' specified for '%s', assuming localhost only", + ros_automatic_discovery_range_env_val, + RCL_AUTOMATIC_DISCOVERY_RANGE_ENV_VAR); + + discovery_options->automatic_discovery_range = RMW_AUTOMATIC_DISCOVERY_RANGE_LOCALHOST; + } + + return RCL_RET_OK; +} + +RCL_PUBLIC +const char * +rcl_automatic_discovery_range_to_string(rmw_automatic_discovery_range_t automatic_discovery_range) +{ + switch (automatic_discovery_range) { + case RMW_AUTOMATIC_DISCOVERY_RANGE_NOT_SET: + return "RMW_AUTOMATIC_DISCOVERY_RANGE_NOT_SET"; + case RMW_AUTOMATIC_DISCOVERY_RANGE_OFF: + return "RMW_AUTOMATIC_DISCOVERY_RANGE_OFF"; + case RMW_AUTOMATIC_DISCOVERY_RANGE_LOCALHOST: + return "RMW_AUTOMATIC_DISCOVERY_RANGE_LOCALHOST"; + case RMW_AUTOMATIC_DISCOVERY_RANGE_SUBNET: + return "RMW_AUTOMATIC_DISCOVERY_RANGE_SUBNET"; + case RMW_AUTOMATIC_DISCOVERY_RANGE_SYSTEM_DEFAULT: + return "RMW_AUTOMATIC_DISCOVERY_RANGE_SYSTEM_DEFAULT"; + default: + return NULL; + } +} + +rcl_ret_t +rcl_get_discovery_static_peers( + rmw_discovery_options_t * discovery_options, + rcutils_allocator_t * allocator) +{ + const char * ros_peers_env_val = NULL; + const char * get_env_error_str = NULL; + + RCUTILS_CAN_SET_MSG_AND_RETURN_WITH_ERROR_OF(RCL_RET_INVALID_ARGUMENT); + RCUTILS_CAN_SET_MSG_AND_RETURN_WITH_ERROR_OF(RCL_RET_ERROR); + RCL_CHECK_ARGUMENT_FOR_NULL(discovery_options, RCL_RET_INVALID_ARGUMENT); + RCL_CHECK_ARGUMENT_FOR_NULL(allocator, RCL_RET_INVALID_ARGUMENT); + + get_env_error_str = rcutils_get_env(RCL_STATIC_PEERS_ENV_VAR, &ros_peers_env_val); + if (NULL != get_env_error_str) { + RCL_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Error getting environment variable '%s': %s", + RCL_STATIC_PEERS_ENV_VAR, get_env_error_str); + return RCL_RET_ERROR; + } + + // The value of the env var should be at least "", even when not set. + if (NULL == ros_peers_env_val) { + RCL_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Environment variable value unexpectedly NULL when checking '%s'", + RCL_STATIC_PEERS_ENV_VAR); + return RCL_RET_ERROR; + } + + rcutils_string_array_t array = rcutils_get_zero_initialized_string_array(); + rcutils_ret_t split_ret = rcutils_split(ros_peers_env_val, ';', *allocator, &array); + if (RCUTILS_RET_OK != split_ret) { + RCL_SET_ERROR_MSG(rcutils_get_error_string().str); + return RCL_RET_ERROR; + } + + rmw_ret_t rmw_ret = rmw_discovery_options_init(discovery_options, array.size, allocator); + if (RMW_RET_OK != rmw_ret) { + RCL_SET_ERROR_MSG(rmw_get_error_string().str); + return rcl_convert_rmw_ret_to_rcl_ret(rmw_ret); + } + + for (size_t i = 0; i < array.size; ++i) { + if (strlen(array.data[i]) > (RMW_DISCOVERY_OPTIONS_STATIC_PEERS_MAX_LENGTH - 1)) { + RCUTILS_LOG_WARN_NAMED( + ROS_PACKAGE_NAME, + "Static peer %s specified to '%s' is too long (maximum of %d); skipping", + array.data[i], RCL_STATIC_PEERS_ENV_VAR, + RMW_DISCOVERY_OPTIONS_STATIC_PEERS_MAX_LENGTH - 1); + continue; + } +#ifdef _WIN32 + strncpy_s( + discovery_options->static_peers[i].peer_address, + RMW_DISCOVERY_OPTIONS_STATIC_PEERS_MAX_LENGTH, + array.data[i], + RMW_DISCOVERY_OPTIONS_STATIC_PEERS_MAX_LENGTH); +#else + strncpy( + discovery_options->static_peers[i].peer_address, + array.data[i], + RMW_DISCOVERY_OPTIONS_STATIC_PEERS_MAX_LENGTH); + discovery_options->static_peers[i].peer_address[ + RMW_DISCOVERY_OPTIONS_STATIC_PEERS_MAX_LENGTH - 1] = '\0'; +#endif + } + + if (RCUTILS_RET_OK != rcutils_string_array_fini(&array)) { + RCL_SET_ERROR_MSG(rcutils_get_error_string().str); + return RCL_RET_ERROR; + } + + return RCL_RET_OK; +} diff --git a/rcl/src/rcl/init.c b/rcl/src/rcl/init.c index 92cd25dc4..9288a5d87 100644 --- a/rcl/src/rcl/init.c +++ b/rcl/src/rcl/init.c @@ -28,6 +28,7 @@ extern "C" #include "tracetools/tracetools.h" #include "rcl/arguments.h" +#include "rcl/discovery_options.h" #include "rcl/domain_id.h" #include "rcl/error_handling.h" #include "rcl/localhost.h" @@ -155,13 +156,84 @@ rcl_init( rmw_localhost_only_t * localhost_only = &context->impl->init_options.impl->rmw_init_options.localhost_only; - if (RMW_LOCALHOST_ONLY_DEFAULT == *localhost_only) { + if (RMW_LOCALHOST_ONLY_DEFAULT != *localhost_only) { + RCUTILS_LOG_WARN_NAMED( + ROS_PACKAGE_NAME, + "'localhost_only' init option is deprecated. Use 'automatic_discovery_range' and " + "'static_peers' instead."); + } else { // Get actual localhost_only value based on environment variable, if needed. ret = rcl_get_localhost_only(localhost_only); if (RCL_RET_OK != ret) { fail_ret = ret; goto fail; } + if (RMW_LOCALHOST_ONLY_DEFAULT != *localhost_only) { + RCUTILS_LOG_WARN_NAMED( + ROS_PACKAGE_NAME, + "ROS_LOCALHOST_ONLY is deprecated. Use ROS_AUTOMATIC_DISCOVERY_RANGE and " + "ROS_STATIC_PEERS instead."); + } + } + + const rmw_discovery_options_t original_discovery_options = + options->impl->rmw_init_options.discovery_options; + rmw_discovery_options_t * discovery_options = + &context->impl->init_options.impl->rmw_init_options.discovery_options; + + // Get actual discovery range option based on environment variable, if not given + // to original options passed to function + if ( // NOLINT + RMW_AUTOMATIC_DISCOVERY_RANGE_NOT_SET == original_discovery_options.automatic_discovery_range) + { + ret = rcl_get_automatic_discovery_range(discovery_options); + if (RCL_RET_OK != ret) { + fail_ret = ret; + goto fail; + } + } + + if (0 == discovery_options->static_peers_count && + discovery_options->automatic_discovery_range != RMW_AUTOMATIC_DISCOVERY_RANGE_OFF) + { + // Get static peers. + // If off is set, it makes sense to not get any static peers. + ret = rcl_get_discovery_static_peers(discovery_options, &allocator); + if (RCL_RET_OK != ret) { + fail_ret = ret; + goto fail; + } + } + + if (discovery_options->static_peers_count > 0 && + discovery_options->automatic_discovery_range == RMW_AUTOMATIC_DISCOVERY_RANGE_OFF) + { + RCUTILS_LOG_WARN_NAMED( + ROS_PACKAGE_NAME, + "Note: ROS_AUTOMATIC_DISCOVERY_RANGE is set to OFF, but " + "found static peers in ROS_STATIC_PEERS. " + "ROS_STATIC_PEERS will be ignored."); + } + + const char * discovery_range_string = + rcl_automatic_discovery_range_to_string(discovery_options->automatic_discovery_range); + if (NULL == discovery_range_string) { + discovery_range_string = "not recognized"; + } + RCUTILS_LOG_DEBUG_NAMED( + ROS_PACKAGE_NAME, + "Automatic discovery range is %s (%d)", + discovery_range_string, + discovery_options->automatic_discovery_range); + RCUTILS_LOG_DEBUG_NAMED( + ROS_PACKAGE_NAME, + "Static peers count is %lu", + discovery_options->static_peers_count); + + for (size_t ii = 0; ii < discovery_options->static_peers_count; ++ii) { + RCUTILS_LOG_DEBUG_NAMED( + ROS_PACKAGE_NAME, + "\t%s", discovery_options->static_peers[ii].peer_address); } if (context->global_arguments.impl->enclave) { diff --git a/rcl/src/rcl/init_options.c b/rcl/src/rcl/init_options.c index 5fa93c70d..ec2c6eecc 100644 --- a/rcl/src/rcl/init_options.c +++ b/rcl/src/rcl/init_options.c @@ -75,6 +75,12 @@ rcl_init_options_init(rcl_init_options_t * init_options, rcl_allocator_t allocat RCL_SET_ERROR_MSG(rmw_get_error_string().str); return rcl_convert_rmw_ret_to_rcl_ret(rmw_ret); } + + // rmw defaults discovery range to LOCALHOST + // Intentionally set discovery options to NOT_SET so we can determine + // if a user intentionally sets them later + init_options->impl->rmw_init_options.discovery_options.automatic_discovery_range = + RMW_AUTOMATIC_DISCOVERY_RANGE_NOT_SET; return RCL_RET_OK; } diff --git a/rcl/src/rcl/localhost.c b/rcl/src/rcl/localhost.c index 74adf3e68..f3acef544 100644 --- a/rcl/src/rcl/localhost.c +++ b/rcl/src/rcl/localhost.c @@ -41,9 +41,12 @@ rcl_get_localhost_only(rmw_localhost_only_t * localhost_only) get_env_error_str); return RCL_RET_ERROR; } - *localhost_only = (ros_local_host_env_val != NULL && - strcmp( - ros_local_host_env_val, - "1") == 0) ? RMW_LOCALHOST_ONLY_ENABLED : RMW_LOCALHOST_ONLY_DISABLED; + if (ros_local_host_env_val == NULL || ros_local_host_env_val[0] == '\0') { + *localhost_only = RMW_LOCALHOST_ONLY_DEFAULT; + } else { + *localhost_only = + strncmp(ros_local_host_env_val, "1", 1) == 0 ? + RMW_LOCALHOST_ONLY_ENABLED : RMW_LOCALHOST_ONLY_DISABLED; + } return RCL_RET_OK; } diff --git a/rcl/test/CMakeLists.txt b/rcl/test/CMakeLists.txt index b93e796d4..771f08202 100644 --- a/rcl/test/CMakeLists.txt +++ b/rcl/test/CMakeLists.txt @@ -424,6 +424,12 @@ rcl_add_custom_gtest(test_validate_enclave_name LIBRARIES ${PROJECT_NAME} mimick ) +rcl_add_custom_gtest(test_discovery_options + SRCS rcl/test_discovery_options.cpp + APPEND_LIBRARY_DIRS ${extra_lib_dirs} + LIBRARIES ${PROJECT_NAME} +) + rcl_add_custom_gtest(test_domain_id SRCS rcl/test_domain_id.cpp APPEND_LIBRARY_DIRS ${extra_lib_dirs} diff --git a/rcl/test/rcl/test_discovery_options.cpp b/rcl/test/rcl/test_discovery_options.cpp new file mode 100644 index 000000000..7cff0a87a --- /dev/null +++ b/rcl/test/rcl/test_discovery_options.cpp @@ -0,0 +1,243 @@ +// Copyright 2022 Open Source Robotics Foundation, Inc. +// +// 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 "rcl/rcl.h" +#include "rcl/discovery_options.h" + +#include "rcutils/allocator.h" +#include "rcutils/env.h" + +#include "rmw/discovery_options.h" + +TEST(TestDiscoveryInfo, test_get_peers) { + rcutils_allocator_t allocator = rcutils_get_default_allocator(); + rmw_discovery_options_t discovery_options_var = rmw_get_zero_initialized_discovery_options(); + + // Retrieve peers if peer list is empty + ASSERT_TRUE(rcutils_set_env("ROS_STATIC_PEERS", "")); + EXPECT_EQ(RCL_RET_OK, rcl_get_discovery_static_peers(&discovery_options_var, &allocator)); + EXPECT_EQ(0u, discovery_options_var.static_peers_count); + EXPECT_EQ(RCL_RET_OK, rmw_discovery_options_fini(&discovery_options_var)); + + // Retrieve peers if peer list has one IPv4 peer + discovery_options_var = rmw_get_zero_initialized_discovery_options(); + ASSERT_TRUE(rcutils_set_env("ROS_STATIC_PEERS", "192.168.0.1")); + EXPECT_EQ(RCL_RET_OK, rcl_get_discovery_static_peers(&discovery_options_var, &allocator)); + EXPECT_EQ(1u, discovery_options_var.static_peers_count); + EXPECT_STREQ("192.168.0.1", discovery_options_var.static_peers[0].peer_address); + EXPECT_EQ(RCL_RET_OK, rmw_discovery_options_fini(&discovery_options_var)); + + // Retrieve peers if peer list has one IPv6 peer + discovery_options_var = rmw_get_zero_initialized_discovery_options(); + ASSERT_TRUE(rcutils_set_env("ROS_STATIC_PEERS", "ceab:78ee:b73a:ec05:0898:0b2c:5ce5:8ed3")); + EXPECT_EQ(RCL_RET_OK, rcl_get_discovery_static_peers(&discovery_options_var, &allocator)); + EXPECT_EQ(1u, discovery_options_var.static_peers_count); + EXPECT_STREQ( + "ceab:78ee:b73a:ec05:0898:0b2c:5ce5:8ed3", + discovery_options_var.static_peers[0].peer_address); + EXPECT_EQ(RCL_RET_OK, rmw_discovery_options_fini(&discovery_options_var)); + + // Retrieve peers if peer list has two IPv4 peers + discovery_options_var = rmw_get_zero_initialized_discovery_options(); + ASSERT_TRUE(rcutils_set_env("ROS_STATIC_PEERS", "192.168.0.1;10.0.0.2")); + EXPECT_EQ(RCL_RET_OK, rcl_get_discovery_static_peers(&discovery_options_var, &allocator)); + EXPECT_EQ(2u, discovery_options_var.static_peers_count); + EXPECT_STREQ("192.168.0.1", discovery_options_var.static_peers[0].peer_address); + EXPECT_STREQ("10.0.0.2", discovery_options_var.static_peers[1].peer_address); + EXPECT_EQ(RCL_RET_OK, rmw_discovery_options_fini(&discovery_options_var)); + + // Retrieve peers if peer list has one IPv6 peer and one IPv4 peer (order reversed) + discovery_options_var = rmw_get_zero_initialized_discovery_options(); + ASSERT_TRUE( + rcutils_set_env("ROS_STATIC_PEERS", "192.168.0.1;ceab:78ee:b73a:ec05:0898:0b2c:5ce5:8ed3")); + EXPECT_EQ(RCL_RET_OK, rcl_get_discovery_static_peers(&discovery_options_var, &allocator)); + EXPECT_EQ(2u, discovery_options_var.static_peers_count); + EXPECT_STREQ("192.168.0.1", discovery_options_var.static_peers[0].peer_address); + EXPECT_STREQ( + "ceab:78ee:b73a:ec05:0898:0b2c:5ce5:8ed3", + discovery_options_var.static_peers[1].peer_address); + EXPECT_EQ(RCL_RET_OK, rmw_discovery_options_fini(&discovery_options_var)); + + // Retrieve peers if peer list has one IPv6 peer and one IPv4 peer + discovery_options_var = rmw_get_zero_initialized_discovery_options(); + ASSERT_TRUE( + rcutils_set_env("ROS_STATIC_PEERS", "ceab:78ee:b73a:ec05:0898:0b2c:5ce5:8ed3;192.168.0.1")); + EXPECT_EQ(RCL_RET_OK, rcl_get_discovery_static_peers(&discovery_options_var, &allocator)); + EXPECT_EQ(2u, discovery_options_var.static_peers_count); + EXPECT_STREQ( + "ceab:78ee:b73a:ec05:0898:0b2c:5ce5:8ed3", + discovery_options_var.static_peers[0].peer_address); + EXPECT_STREQ("192.168.0.1", discovery_options_var.static_peers[1].peer_address); + EXPECT_EQ(RCL_RET_OK, rmw_discovery_options_fini(&discovery_options_var)); + + // Retrieve peers if peer list has one two IPv4 peers with subnet mask + discovery_options_var = rmw_get_zero_initialized_discovery_options(); + ASSERT_TRUE(rcutils_set_env("ROS_STATIC_PEERS", "10.1.2.3;192.168.0.0/24")); + EXPECT_EQ(RCL_RET_OK, rcl_get_discovery_static_peers(&discovery_options_var, &allocator)); + EXPECT_EQ(2u, discovery_options_var.static_peers_count); + EXPECT_STREQ("10.1.2.3", discovery_options_var.static_peers[0].peer_address); + EXPECT_STREQ("192.168.0.0/24", discovery_options_var.static_peers[1].peer_address); + EXPECT_EQ(RCL_RET_OK, rmw_discovery_options_fini(&discovery_options_var)); + + // Retrieve peers if peer list is empty + discovery_options_var = rmw_get_zero_initialized_discovery_options(); + ASSERT_TRUE(rcutils_set_env("ROS_STATIC_PEERS", ";")); + EXPECT_EQ(RCL_RET_OK, rcl_get_discovery_static_peers(&discovery_options_var, &allocator)); + EXPECT_EQ(0u, discovery_options_var.static_peers_count); + EXPECT_EQ(RCL_RET_OK, rmw_discovery_options_fini(&discovery_options_var)); + + // Retrieve peer with trailing ; + discovery_options_var = rmw_get_zero_initialized_discovery_options(); + ASSERT_TRUE(rcutils_set_env("ROS_STATIC_PEERS", "192.168.0.1;")); + EXPECT_EQ(RCL_RET_OK, rcl_get_discovery_static_peers(&discovery_options_var, &allocator)); + EXPECT_EQ(1u, discovery_options_var.static_peers_count); + EXPECT_STREQ("192.168.0.1", discovery_options_var.static_peers[0].peer_address); + EXPECT_EQ(RCL_RET_OK, rmw_discovery_options_fini(&discovery_options_var)); + + // Retrieve peer with starting ; + discovery_options_var = rmw_get_zero_initialized_discovery_options(); + ASSERT_TRUE(rcutils_set_env("ROS_STATIC_PEERS", ";192.168.0.1")); + EXPECT_EQ(RCL_RET_OK, rcl_get_discovery_static_peers(&discovery_options_var, &allocator)); + EXPECT_EQ(1u, discovery_options_var.static_peers_count); + EXPECT_STREQ("192.168.0.1", discovery_options_var.static_peers[0].peer_address); + EXPECT_EQ(RCL_RET_OK, rmw_discovery_options_fini(&discovery_options_var)); + + // Retrieve peer with FQDN + discovery_options_var = rmw_get_zero_initialized_discovery_options(); + ASSERT_TRUE(rcutils_set_env("ROS_STATIC_PEERS", "example.com")); + EXPECT_EQ(RCL_RET_OK, rcl_get_discovery_static_peers(&discovery_options_var, &allocator)); + EXPECT_EQ(1u, discovery_options_var.static_peers_count); + EXPECT_STREQ("example.com", discovery_options_var.static_peers[0].peer_address); + EXPECT_EQ(RCL_RET_OK, rmw_discovery_options_fini(&discovery_options_var)); + + // Retrieve peer with FQDN and IPv4 + discovery_options_var = rmw_get_zero_initialized_discovery_options(); + ASSERT_TRUE(rcutils_set_env("ROS_STATIC_PEERS", "example.com;192.168.0.1")); + EXPECT_EQ(RCL_RET_OK, rcl_get_discovery_static_peers(&discovery_options_var, &allocator)); + EXPECT_EQ(2u, discovery_options_var.static_peers_count); + EXPECT_STREQ("example.com", discovery_options_var.static_peers[0].peer_address); + EXPECT_STREQ("192.168.0.1", discovery_options_var.static_peers[1].peer_address); + EXPECT_EQ(RCL_RET_OK, rmw_discovery_options_fini(&discovery_options_var)); +} + +TEST(TestDiscoveryInfo, test_get_automatic_discovery_range) { + ASSERT_TRUE(rcutils_set_env("ROS_STATIC_PEERS", "")); + rmw_discovery_options_t discovery_options_var = rmw_get_zero_initialized_discovery_options(); + + // Set unexpected discovery range. Should default to LOCALHOST + discovery_options_var = rmw_get_zero_initialized_discovery_options(); + ASSERT_TRUE(rcutils_set_env("ROS_AUTOMATIC_DISCOVERY_RANGE", "0")); + EXPECT_EQ(RCL_RET_OK, rcl_get_automatic_discovery_range(&discovery_options_var)); + EXPECT_EQ( + RMW_AUTOMATIC_DISCOVERY_RANGE_LOCALHOST, + discovery_options_var.automatic_discovery_range); + + // Set discovery range to OFF + discovery_options_var = rmw_get_zero_initialized_discovery_options(); + ASSERT_TRUE(rcutils_set_env("ROS_AUTOMATIC_DISCOVERY_RANGE", "OFF")); + EXPECT_EQ(RCL_RET_OK, rcl_get_automatic_discovery_range(&discovery_options_var)); + EXPECT_EQ(RMW_AUTOMATIC_DISCOVERY_RANGE_OFF, discovery_options_var.automatic_discovery_range); + + // Set discovery range to LOCALHOST + discovery_options_var = rmw_get_zero_initialized_discovery_options(); + ASSERT_TRUE(rcutils_set_env("ROS_AUTOMATIC_DISCOVERY_RANGE", "LOCALHOST")); + EXPECT_EQ(RCL_RET_OK, rcl_get_automatic_discovery_range(&discovery_options_var)); + EXPECT_EQ( + RMW_AUTOMATIC_DISCOVERY_RANGE_LOCALHOST, + discovery_options_var.automatic_discovery_range); + + // Set discovery range to SUBNET + discovery_options_var = rmw_get_zero_initialized_discovery_options(); + ASSERT_TRUE(rcutils_set_env("ROS_AUTOMATIC_DISCOVERY_RANGE", "SUBNET")); + EXPECT_EQ(RCL_RET_OK, rcl_get_automatic_discovery_range(&discovery_options_var)); + EXPECT_EQ(RMW_AUTOMATIC_DISCOVERY_RANGE_SUBNET, discovery_options_var.automatic_discovery_range); + + // Set unexpected discovery range. Should default to LOCALHOST + discovery_options_var = rmw_get_zero_initialized_discovery_options(); + ASSERT_TRUE(rcutils_set_env("ROS_AUTOMATIC_DISCOVERY_RANGE", "Unexpected")); + EXPECT_EQ(RCL_RET_OK, rcl_get_automatic_discovery_range(&discovery_options_var)); + EXPECT_EQ( + RMW_AUTOMATIC_DISCOVERY_RANGE_LOCALHOST, + discovery_options_var.automatic_discovery_range); +} + +TEST(TestDiscoveryInfo, test_bad_argument) { + rcutils_allocator_t allocator = rcutils_get_default_allocator(); + + EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, rcl_get_automatic_discovery_range(nullptr)); + EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, rcl_get_discovery_static_peers(nullptr, &allocator)); + + rmw_discovery_options_t discovery_options_var = rmw_get_zero_initialized_discovery_options(); + EXPECT_EQ( + RCL_RET_INVALID_ARGUMENT, + rcl_get_discovery_static_peers(&discovery_options_var, nullptr)); + EXPECT_EQ(RCL_RET_OK, rmw_discovery_options_fini(&discovery_options_var)); +} + +// Since the two functions operate on the same variable instance, make sure they don't interfere +TEST(TestDiscoveryInfo, test_get_both) { + rcutils_allocator_t allocator = rcutils_get_default_allocator(); + rmw_discovery_options_t discovery_options_var = rmw_get_zero_initialized_discovery_options(); + + ASSERT_TRUE(rcutils_set_env("ROS_STATIC_PEERS", "")); + ASSERT_TRUE(rcutils_set_env("ROS_AUTOMATIC_DISCOVERY_RANGE", "0")); + EXPECT_EQ(RCL_RET_OK, rcl_get_automatic_discovery_range(&discovery_options_var)); + EXPECT_EQ(RCL_RET_OK, rcl_get_discovery_static_peers(&discovery_options_var, &allocator)); + EXPECT_EQ( + RMW_AUTOMATIC_DISCOVERY_RANGE_LOCALHOST, + discovery_options_var.automatic_discovery_range); + EXPECT_EQ(0u, discovery_options_var.static_peers_count); + EXPECT_EQ(RCL_RET_OK, rmw_discovery_options_fini(&discovery_options_var)); + + discovery_options_var = rmw_get_zero_initialized_discovery_options(); + ASSERT_TRUE( + rcutils_set_env("ROS_STATIC_PEERS", "192.168.0.1;ceab:78ee:b73a:ec05:0898:0b2c:5ce5:8ed3")); + ASSERT_TRUE(rcutils_set_env("ROS_AUTOMATIC_DISCOVERY_RANGE", "LOCALHOST")); + EXPECT_EQ(RCL_RET_OK, rcl_get_automatic_discovery_range(&discovery_options_var)); + EXPECT_EQ(RCL_RET_OK, rcl_get_discovery_static_peers(&discovery_options_var, &allocator)); + EXPECT_EQ( + RMW_AUTOMATIC_DISCOVERY_RANGE_LOCALHOST, + discovery_options_var.automatic_discovery_range); + EXPECT_EQ(2u, discovery_options_var.static_peers_count); + EXPECT_STREQ("192.168.0.1", discovery_options_var.static_peers[0].peer_address); + EXPECT_STREQ( + "ceab:78ee:b73a:ec05:0898:0b2c:5ce5:8ed3", + discovery_options_var.static_peers[1].peer_address); + EXPECT_EQ(RCL_RET_OK, rmw_discovery_options_fini(&discovery_options_var)); + + discovery_options_var = rmw_get_zero_initialized_discovery_options(); + ASSERT_TRUE( + rcutils_set_env("ROS_STATIC_PEERS", "192.168.0.1;ceab:78ee:b73a:ec05:0898:0b2c:5ce5:8ed3")); + ASSERT_TRUE(rcutils_set_env("ROS_AUTOMATIC_DISCOVERY_RANGE", "SUBNET")); + EXPECT_EQ(RCL_RET_OK, rcl_get_automatic_discovery_range(&discovery_options_var)); + EXPECT_EQ(RCL_RET_OK, rcl_get_discovery_static_peers(&discovery_options_var, &allocator)); + EXPECT_EQ(RMW_AUTOMATIC_DISCOVERY_RANGE_SUBNET, discovery_options_var.automatic_discovery_range); + EXPECT_EQ(2u, discovery_options_var.static_peers_count); + EXPECT_STREQ("192.168.0.1", discovery_options_var.static_peers[0].peer_address); + EXPECT_STREQ( + "ceab:78ee:b73a:ec05:0898:0b2c:5ce5:8ed3", + discovery_options_var.static_peers[1].peer_address); + EXPECT_EQ(RCL_RET_OK, rmw_discovery_options_fini(&discovery_options_var)); + + discovery_options_var = rmw_get_zero_initialized_discovery_options(); + ASSERT_TRUE(rcutils_set_env("ROS_STATIC_PEERS", "")); + ASSERT_TRUE(rcutils_set_env("ROS_AUTOMATIC_DISCOVERY_RANGE", "SUBNET")); + EXPECT_EQ(RCL_RET_OK, rcl_get_automatic_discovery_range(&discovery_options_var)); + EXPECT_EQ(RCL_RET_OK, rcl_get_discovery_static_peers(&discovery_options_var, &allocator)); + EXPECT_EQ(RMW_AUTOMATIC_DISCOVERY_RANGE_SUBNET, discovery_options_var.automatic_discovery_range); + EXPECT_EQ(0u, discovery_options_var.static_peers_count); + EXPECT_EQ(RCL_RET_OK, rmw_discovery_options_fini(&discovery_options_var)); +}