diff --git a/rcl/CMakeLists.txt b/rcl/CMakeLists.txt index d96c055..361815a 100644 --- a/rcl/CMakeLists.txt +++ b/rcl/CMakeLists.txt @@ -26,6 +26,7 @@ endif() set(${PROJECT_NAME}_sources src/rcl/client.c src/rcl/common.c + src/rcl/expand_topic_name.c src/rcl/graph.c src/rcl/guard_condition.c src/rcl/node.c @@ -37,6 +38,7 @@ set(${PROJECT_NAME}_sources src/rcl/time.c ${time_impl_c} src/rcl/timer.c + src/rcl/validate_topic_name.c src/rcl/wait.c ) diff --git a/rcl/include/rcl/expand_topic_name.h b/rcl/include/rcl/expand_topic_name.h new file mode 100644 index 0000000..1c6a5b5 --- /dev/null +++ b/rcl/include/rcl/expand_topic_name.h @@ -0,0 +1,134 @@ +// Copyright 2017 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. + +#ifndef RCL__EXPAND_TOPIC_NAME_H_ +#define RCL__EXPAND_TOPIC_NAME_H_ + +#if __cplusplus +extern "C" +{ +#endif + +#include "rcutils/types/string_map.h" +#include "rcl/allocator.h" +#include "rcl/macros.h" +#include "rcl/types.h" +#include "rcl/visibility_control.h" + +/// Expand a given topic name into a fully-qualified topic name. +/** + * The input_topic_name, node_name, and node_namespace arguments must all be + * vaid, null terminated c strings. + * The output_topic_name will not be assigned a value in the event of an error. + * + * The output_topic_name will be null terminated. + * It is also allocated, so it needs to be deallocated, when it is no longer + * needed, with the same allocator given to this function. + * Make sure the `char *` which is passed for the output_topic_name does not + * point to allocated memory before calling this function, because it will be + * overwritten and therefore leaked if this function is successful. + * + * Expected usage: + * + * ```c + * rcl_allocator_t allocator = rcl_get_default_allocator(); + * char * expanded_topic_name = NULL; + * rcl_ret_t ret = rcl_expand_topic_name( + * "some/topic", + * "my_node", + * "my_ns", + * rcl_get_default_topic_name_substitutions(), + * allocator, + * &expanded_topic_name); + * if (ret != RCL_RET_OK) { + * // ... error handling + * } else { + * printf("Expanded topic name: %s\n", expanded_topic_name); + * // ... when done the output topic name needs to be deallocated: + * allocator.deallocate(expanded_topic_name, allocator.state); + * } + * ``` + * + * The input topic name is validated using rcl_validate_topic_name(), + * but if it fails validation RCL_RET_TOPIC_NAME_INVALID is returned. + * + * The input node name is validated using rmw_validate_node_name(), + * but if it fails validation RCL_RET_NODE_INVALID_NAME is returned. + * + * The input node namespace is validated using rmw_validate_namespace(), + * but if it fails validation RCL_RET_NODE_INVALID_NAMESPACE is returned. + * + * In addition to what is given by rcl_get_default_topic_name_substitutions(), + * there are there these substitutions: + * + * - {node} -> the name of the node + * - {namespace} -> the namespace of the node + * - {ns} -> the namespace of the node + * + * If an unknown substitution is used, RCL_RET_UNKNOWN_SUBSTITUTION is returned. + * + *
+ * Attribute | Adherence + * ------------------ | ------------- + * Allocates Memory | Yes + * Thread-Safe | No + * Uses Atomics | No + * Lock-Free | Yes + * + * \param[in] input_topic_name topic name to be expanded + * \param[in] node_name name of the node associated with the topic + * \param[in] node_namespace namespace of the node associated with the topic + * \param[in] substitutions string map with possible substitutions + * \param[in] allocator the allocator to be used when creating the output topic + * \param[out] output_topic_name output char * pointer + * \return `RCL_RET_OK` if the topic name was expanded successfully, or + * \return `RCL_RET_INVALID_ARGUMENT` if any arguments are invalid, or + * \return `RCL_RET_BAD_ALLOC` if allocating memory failed, or + * \return `RCL_RET_TOPIC_NAME_INVALID` if the given topic name is invalid, or + * \return `RCL_RET_NODE_INVALID_NAME` if the name is invalid, or + * \return `RCL_RET_NODE_INVALID_NAMESPACE` if the namespace_ is invalid, or + * \return `RCL_RET_UNKNOWN_SUBSTITUTION` if the given topic name is invalid, or + * \return `RCL_RET_ERROR` if an unspecified error occurs. + */ +RCL_PUBLIC +RCL_WARN_UNUSED +rcl_ret_t +rcl_expand_topic_name( + const char * input_topic_name, + const char * node_name, + const char * node_namespace, + const rcutils_string_map_t * substitutions, + rcl_allocator_t allocator, + char ** output_topic_name); + +/// Fill a given string map with the default substitution pairs. +/** + * If the string map is not initialized RCL_RET_INVALID_ARGUMENT is returned. + * + * \param[inout] string_map rcutils_string_map_t map to be filled with pairs + * \return `RCL_RET_OK` if successfully, or + * \return `RCL_RET_INVALID_ARGUMENT` if any arguments are invalid, or + * \return `RCL_RET_BAD_ALLOC` if allocating memory failed, or + * \return `RCL_RET_ERROR` if an unspecified error occurs. + */ +RCL_PUBLIC +RCL_WARN_UNUSED +rcl_ret_t +rcl_get_default_topic_name_substitutions(rcutils_string_map_t * string_map); + +#if __cplusplus +} +#endif + +#endif // RCL__EXPAND_TOPIC_NAME_H_ diff --git a/rcl/include/rcl/types.h b/rcl/include/rcl/types.h index 9062c1b..c989444 100644 --- a/rcl/include/rcl/types.h +++ b/rcl/include/rcl/types.h @@ -36,6 +36,10 @@ typedef rmw_ret_t rcl_ret_t; #define RCL_RET_NOT_INIT 101 /// Mismatched rmw identifier return code. #define RCL_RET_MISMATCHED_RMW_ID 102 +/// Topic name does not pass validation. +#define RCL_RET_TOPIC_NAME_INVALID 103 +/// Topic name substitution is unknown. +#define RCL_RET_UNKNOWN_SUBSTITUTION 104 // rcl node specific ret codes in 2XX /// Invalid rcl_node_t given return code. diff --git a/rcl/include/rcl/validate_topic_name.h b/rcl/include/rcl/validate_topic_name.h new file mode 100644 index 0000000..4d09155 --- /dev/null +++ b/rcl/include/rcl/validate_topic_name.h @@ -0,0 +1,107 @@ +// Copyright 2017 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. + +#ifndef RCL__VALIDATE_TOPIC_NAME_H_ +#define RCL__VALIDATE_TOPIC_NAME_H_ + +#if __cplusplus +extern "C" +{ +#endif + +#include "rcl/macros.h" +#include "rcl/types.h" +#include "rcl/visibility_control.h" + +#define RCL_TOPIC_NAME_VALID 0 +#define RCL_TOPIC_NAME_INVALID_IS_EMPTY_STRING 1 +#define RCL_TOPIC_NAME_INVALID_ENDS_WITH_FORWARD_SLASH 2 +#define RCL_TOPIC_NAME_INVALID_CONTAINS_UNALLOWED_CHARACTERS 3 +#define RCL_TOPIC_NAME_INVALID_NAME_TOKEN_STARTS_WITH_NUMBER 4 +#define RCL_TOPIC_NAME_INVALID_UNMATCHED_CURLY_BRACE 5 +#define RCL_TOPIC_NAME_INVALID_MISPLACED_TILDE 6 +#define RCL_TOPIC_NAME_INVALID_TILDE_NOT_FOLLOWED_BY_FORWARD_SLASH 7 +#define RCL_TOPIC_NAME_INVALID_SUBSTITUTION_CONTAINS_UNALLOWED_CHARACTERS 8 +#define RCL_TOPIC_NAME_INVALID_SUBSTITUTION_STARTS_WITH_NUMBER 9 + +/// Validate a given topic name. +/** + * The topic name does not need to be a full qualified name, but it should + * follow some of the rules in this document: + * + * http://design.ros2.org/articles/topic_and_service_names.html + * + * Note that this function expects any URL suffixes as described in the above + * document to have already been removed. + * + * If the input topic is valid, RCL_TOPIC_NAME_VALID will be stored + * into validation_result. + * If the input topic violates any of the rules then one of these values will + * be stored into validation_result: + * + * - RCL_TOPIC_NAME_INVALID_IS_EMPTY_STRING + * - RCL_TOPIC_NAME_INVALID_ENDS_WITH_FORWARD_SLASH + * - RCL_TOPIC_NAME_INVALID_CONTAINS_UNALLOWED_CHARACTERS + * - RCL_TOPIC_NAME_INVALID_NAME_TOKEN_STARTS_WITH_NUMBER + * - RCL_TOPIC_NAME_INVALID_UNMATCHED_CURLY_BRACE + * - RCL_TOPIC_NAME_INVALID_MISPLACED_TILDE + * - RCL_TOPIC_NAME_INVALID_TILDE_NOT_FOLLOWED_BY_FORWARD_SLASH + * - RCL_TOPIC_NAME_INVALID_SUBSTITUTION_CONTAINS_UNALLOWED_CHARACTERS + * - RCL_TOPIC_NAME_INVALID_SUBSTITUTION_STARTS_WITH_NUMBER + * + * Some checks, like the check for illegal repeated forward slashes, are not + * checked in this function because they would need to be checked again after + * expansion anyways. + * The purpose of this subset of checks is to try to catch issues with content + * that will be expanded in some way by rcl_expand_topic_name(), like `~` or + * substitutions inside of `{}`, or with other issues that would obviously + * prevent expansion, like RCL_TOPIC_NAME_INVALID_IS_EMPTY_STRING. + * + * This function does not check that the substitutions are known substitutions, + * only that the contents of the `{}` follow the rules outline in the document + * which was linked to above. + * + * Stricter validation can be done with rmw_validate_full_topic_name() after using + * rcl_expand_topic_name(). + * + * Additionally, if the invalid_index argument is not NULL, then it will be + * assigned the index in the topic_name string where the violation occurs. + * The invalid_index is not set if the validation passes. + * + * \param[in] topic_name the topic name to be validated, must be null terminated + * \param[out] validation_result the reason for validation failure, if any + * \param[out] invalid_index index of violation if the input topic is invalid + * \return `RCL_RET_OK` if the topic name was expanded successfully, or + * \return `RCL_RET_INVALID_ARGUMENT` if any arguments are invalid, or + * \return `RCL_RET_ERROR` if an unspecified error occurs. + */ +RCL_PUBLIC +RCL_WARN_UNUSED +rcl_ret_t +rcl_validate_topic_name( + const char * topic_name, + int * validation_result, + size_t * invalid_index); + +/// Return a validation result description, or NULL if unknown or RCL_TOPIC_NAME_VALID. +RCL_PUBLIC +RCL_WARN_UNUSED +const char * +rcl_topic_name_validation_result_string(int validation_result); + +#if __cplusplus +} +#endif + +#endif // RCL__VALIDATE_TOPIC_NAME_H_ diff --git a/rcl/src/rcl/expand_topic_name.c b/rcl/src/rcl/expand_topic_name.c new file mode 100644 index 0000000..5afa835 --- /dev/null +++ b/rcl/src/rcl/expand_topic_name.c @@ -0,0 +1,258 @@ +// Copyright 2017 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. + +#if __cplusplus +extern "C" +{ +#endif + +#include "rcl/expand_topic_name.h" + +#include +#include + +#include "./common.h" +#include "rcl/error_handling.h" +#include "rcl/types.h" +#include "rcl/validate_topic_name.h" +#include "rcutils/format_string.h" +#include "rcutils/repl_str.h" +#include "rcutils/strdup.h" +#include "rmw/error_handling.h" +#include "rmw/types.h" +#include "rmw/validate_namespace.h" +#include "rmw/validate_node_name.h" + +#define SAFE_FWRITE_TO_STDERR(msg) fwrite(msg, sizeof(char), sizeof(msg), stderr) + +// built-in substitution strings +#define SUBSTITUION_NODE_NAME "{node}" +#define SUBSTITUION_NAMESPACE "{ns}" +#define SUBSTITUION_NAMESPACE2 "{namespace}" + +rcl_ret_t +rcl_expand_topic_name( + const char * input_topic_name, + const char * node_name, + const char * node_namespace, + const rcutils_string_map_t * substitutions, + rcl_allocator_t allocator, + char ** output_topic_name) +{ + // check arguments that could be null + RCL_CHECK_ARGUMENT_FOR_NULL(input_topic_name, RCL_RET_INVALID_ARGUMENT, allocator) + RCL_CHECK_ARGUMENT_FOR_NULL(node_name, RCL_RET_INVALID_ARGUMENT, allocator) + RCL_CHECK_ARGUMENT_FOR_NULL(node_namespace, RCL_RET_INVALID_ARGUMENT, allocator) + RCL_CHECK_ARGUMENT_FOR_NULL(substitutions, RCL_RET_INVALID_ARGUMENT, allocator) + RCL_CHECK_ARGUMENT_FOR_NULL(output_topic_name, RCL_RET_INVALID_ARGUMENT, allocator) + // validate the input topic + int validation_result; + rcl_ret_t ret = rcl_validate_topic_name(input_topic_name, &validation_result, NULL); + if (ret != RCL_RET_OK) { + // error message already set + return ret; + } + if (validation_result != RCL_TOPIC_NAME_VALID) { + RCL_SET_ERROR_MSG("topic name is invalid", allocator) + return RCL_RET_TOPIC_NAME_INVALID; + } + // validate the node name + rmw_ret_t rmw_ret; + rmw_ret = rmw_validate_node_name(node_name, &validation_result, NULL); + if (rmw_ret != RMW_RET_OK) { + RCL_SET_ERROR_MSG(rmw_get_error_string_safe(), allocator) + switch (rmw_ret) { + case RMW_RET_INVALID_ARGUMENT: + return RCL_RET_INVALID_ARGUMENT; + case RMW_RET_ERROR: + // fall through on purpose + default: + return RCL_RET_ERROR; + } + } + if (validation_result != RMW_NODE_NAME_VALID) { + RCL_SET_ERROR_MSG("node name is invalid", allocator) + return RCL_RET_NODE_INVALID_NAME; + } + // validate the namespace + rmw_ret = rmw_validate_namespace(node_namespace, &validation_result, NULL); + if (rmw_ret != RMW_RET_OK) { + RCL_SET_ERROR_MSG(rmw_get_error_string_safe(), allocator) + switch (rmw_ret) { + case RMW_RET_INVALID_ARGUMENT: + return RCL_RET_INVALID_ARGUMENT; + case RMW_RET_ERROR: + // fall through on purpose + default: + return RCL_RET_ERROR; + } + } + if (validation_result != RMW_NODE_NAME_VALID) { + RCL_SET_ERROR_MSG("node namespace is invalid", allocator) + return RCL_RET_NODE_INVALID_NAMESPACE; + } + // check if the topic has substitutions to be made + bool has_a_substitution = strchr(input_topic_name, '{') != NULL; + bool has_a_namespace_tilde = input_topic_name[0] == '~'; + bool is_absolute = input_topic_name[0] == '/'; + // if absolute and doesn't have any substitution + if (is_absolute && !has_a_substitution) { + // nothing to do, duplicate and return + *output_topic_name = rcutils_strdup(input_topic_name, allocator); + if (!*output_topic_name) { + *output_topic_name = NULL; + RCL_SET_ERROR_MSG("failed to allocate memory for output topic", allocator) + return RCL_RET_BAD_ALLOC; + } + return RCL_RET_OK; + } + char * local_output = NULL; + // if has_a_namespace_tilde, replace that first + if (has_a_namespace_tilde) { + // special case where node_namespace is just '/' + // then no additional separating '/' is needed + const char * fmt = (strlen(node_namespace) == 1) ? "%s%s%s" : "%s/%s%s"; + local_output = + rcutils_format_string(allocator, fmt, node_namespace, node_name, input_topic_name + 1); + if (!local_output) { + *output_topic_name = NULL; + RCL_SET_ERROR_MSG("failed to allocate memory for output topic", allocator) + return RCL_RET_BAD_ALLOC; + } + } + // if it has any substitutions, replace those + if (has_a_substitution) { + // Assumptions entering this scope about the topic string: + // + // - All {} are matched and balanced + // - There is no nesting, i.e. {{}} + // - There are no empty substitution substr, i.e. '{}' versus '{something}' + // + // These assumptions are taken because this is checked in the validation function. + const char * current_output = (local_output) ? local_output : input_topic_name; + char * next_opening_brace = NULL; + // current_output may be replaced on each loop if a substitution is made + while ((next_opening_brace = strchr(current_output, '{')) != NULL) { + char * next_closing_brace = strchr(current_output, '}'); + // conclusion based on above assumptions: next_closing_brace - next_opening_brace > 1 + size_t substitution_substr_len = next_closing_brace - next_opening_brace + 1; + // figure out what the replacement is for this substitution + const char * replacement = NULL; + if (strncmp(SUBSTITUION_NODE_NAME, next_opening_brace, substitution_substr_len) == 0) { + replacement = node_name; + } else if ( // NOLINT + strncmp(SUBSTITUION_NAMESPACE, next_opening_brace, substitution_substr_len) == 0 || + strncmp(SUBSTITUION_NAMESPACE2, next_opening_brace, substitution_substr_len) == 0) + { + replacement = node_namespace; + } else { + replacement = rcutils_string_map_getn( + substitutions, + // compare {substitution} + // ^ until ^ + next_opening_brace + 1, substitution_substr_len - 2); + if (!replacement) { + // in this case, it is neither node name nor ns nor in the substitutions map, so error + *output_topic_name = NULL; + char * unmatched_substitution = + rcutils_strndup(next_opening_brace, substitution_substr_len, allocator); + char * allocated_msg = NULL; + char * msg = NULL; + if (unmatched_substitution) { + allocated_msg = rcutils_format_string( + allocator, + "unknown substitution: %s", unmatched_substitution); + msg = allocated_msg; + } else { + SAFE_FWRITE_TO_STDERR("failed to allocate memory for error message\n"); + msg = "unknown substitution: allocation failed when reporting error"; + } + RCL_SET_ERROR_MSG(msg, allocator) + allocator.deallocate(unmatched_substitution, allocator.state); + allocator.deallocate(allocated_msg, allocator.state); + allocator.deallocate(local_output, allocator.state); + return RCL_RET_UNKNOWN_SUBSTITUTION; + } + } + // at this point replacement will be set or an error would have returned out + // do the replacement + char * next_substitution = + rcutils_strndup(next_opening_brace, substitution_substr_len, allocator); + if (!next_substitution) { + *output_topic_name = NULL; + RCL_SET_ERROR_MSG("failed to allocate memory for substitution", allocator) + allocator.deallocate(local_output, allocator.state); + return RCL_RET_BAD_ALLOC; + } + char * original_local_output = local_output; + local_output = rcutils_repl_str(current_output, next_substitution, replacement, &allocator); + allocator.deallocate(next_substitution, allocator.state); // free no matter what + allocator.deallocate(original_local_output, allocator.state); // free no matter what + if (!local_output) { + *output_topic_name = NULL; + RCL_SET_ERROR_MSG("failed to allocate memory for expanded topic", allocator) + return RCL_RET_BAD_ALLOC; + } + current_output = local_output; + // loop until all substitutions are replaced + } // while + } + // finally make the name absolute if it isn't already + if ( + (local_output && local_output[0] != '/') || + (!local_output && input_topic_name[0] != '/')) + { + char * original_local_output = local_output; + // special case where node_namespace is just '/' + // then no additional separating '/' is needed + const char * fmt = (strlen(node_namespace) == 1) ? "%s%s" : "%s/%s"; + local_output = rcutils_format_string( + allocator, fmt, node_namespace, (local_output) ? local_output : input_topic_name); + if (original_local_output) { + allocator.deallocate(original_local_output, allocator.state); + } + if (!local_output) { + *output_topic_name = NULL; + RCL_SET_ERROR_MSG("failed to allocate memory for output topic", allocator) + return RCL_RET_BAD_ALLOC; + } + } + // if the original input_topic_name has not yet be copied into new memory, strdup it now + if (!local_output) { + local_output = rcutils_strdup(input_topic_name, allocator); + if (!local_output) { + *output_topic_name = NULL; + RCL_SET_ERROR_MSG("failed to allocate memory for output topic", allocator) + return RCL_RET_BAD_ALLOC; + } + } + // finally store the result in the out pointer and return + *output_topic_name = local_output; + return RCL_RET_OK; +} + +rcl_ret_t +rcl_get_default_topic_name_substitutions(rcutils_string_map_t * string_map) +{ + RCL_CHECK_ARGUMENT_FOR_NULL( + string_map, RCL_RET_INVALID_ARGUMENT, rcutils_get_default_allocator()) + + // right now there are no default substitutions + + return RCL_RET_OK; +} + +#if __cplusplus +} +#endif diff --git a/rcl/src/rcl/node.c b/rcl/src/rcl/node.c index 60dc46d..d27c822 100644 --- a/rcl/src/rcl/node.c +++ b/rcl/src/rcl/node.c @@ -29,7 +29,6 @@ extern "C" #include "rmw/rmw.h" #include "rmw/validate_namespace.h" #include "rmw/validate_node_name.h" -#include "rmw/validate_topic_name.h" #include "./common.h" diff --git a/rcl/src/rcl/validate_topic_name.c b/rcl/src/rcl/validate_topic_name.c new file mode 100644 index 0000000..b0949d4 --- /dev/null +++ b/rcl/src/rcl/validate_topic_name.c @@ -0,0 +1,222 @@ +// Copyright 2017 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. + +#if __cplusplus +extern "C" +{ +#endif + +#include "rcl/validate_topic_name.h" + +#include +#include + +#include "./common.h" +#include "rcl/allocator.h" +#include "rcutils/isalnum_no_locale.h" + +rcl_ret_t +rcl_validate_topic_name( + const char * topic_name, + int * validation_result, + size_t * invalid_index) +{ + rcl_allocator_t allocator = rcutils_get_default_allocator(); + RCL_CHECK_ARGUMENT_FOR_NULL(topic_name, RCL_RET_INVALID_ARGUMENT, allocator) + RCL_CHECK_ARGUMENT_FOR_NULL(validation_result, RCL_RET_INVALID_ARGUMENT, allocator) + + size_t topic_name_length = strlen(topic_name); + if (topic_name_length == 0) { + *validation_result = RCL_TOPIC_NAME_INVALID_IS_EMPTY_STRING; + if (invalid_index) { + *invalid_index = 0; + } + return RCL_RET_OK; + } + // check that the first character is not a number + if (isdigit(topic_name[0]) != 0) { + // this is the case where the topic is relative and the first token starts with a number + // e.g. 7foo/bar is invalid + *validation_result = RCL_TOPIC_NAME_INVALID_NAME_TOKEN_STARTS_WITH_NUMBER; + if (invalid_index) { + *invalid_index = 0; + } + return RCL_RET_OK; + } + // note topic_name_length is >= 1 at this point + if (topic_name[topic_name_length - 1] == '/') { + // catches both "/foo/" and "/" + *validation_result = RCL_TOPIC_NAME_INVALID_ENDS_WITH_FORWARD_SLASH; + if (invalid_index) { + *invalid_index = topic_name_length - 1; + } + return RCL_RET_OK; + } + // check for unallowed characters, nested and unmatched {} too + bool in_open_curly_brace = false; + size_t opening_curly_brace_index = 0; + for (size_t i = 0; i < topic_name_length; ++i) { + if (rcutils_isalnum_no_locale(topic_name[i])) { + // if within curly braces and the first character is a number, error + // e.g. foo/{4bar} is invalid + if ( + isdigit(topic_name[i]) != 0 && + in_open_curly_brace && + i > 0 && + (i - 1 == opening_curly_brace_index)) + { + *validation_result = RCL_TOPIC_NAME_INVALID_SUBSTITUTION_STARTS_WITH_NUMBER; + if (invalid_index) { + *invalid_index = i; + } + return RCL_RET_OK; + } + // if it is an alpha numeric character, i.e. [0-9|A-Z|a-z], continue + continue; + } else if (topic_name[i] == '_') { + // if it is an underscore, continue + continue; + } else if (topic_name[i] == '/') { + // if it is a forward slash within {}, error + if (in_open_curly_brace) { + *validation_result = RCL_TOPIC_NAME_INVALID_SUBSTITUTION_CONTAINS_UNALLOWED_CHARACTERS; + if (invalid_index) { + *invalid_index = i; + } + return RCL_RET_OK; + } + // if it is a forward slash outside of {}, continue + continue; + } else if (topic_name[i] == '~') { + // if it is a tilde not in the first position, validation fails + if (i != 0) { + *validation_result = RCL_TOPIC_NAME_INVALID_MISPLACED_TILDE; + if (invalid_index) { + *invalid_index = i; + } + return RCL_RET_OK; + } + // if it is a tilde in the first position, continue + continue; + } else if (topic_name[i] == '{') { + opening_curly_brace_index = i; + // if starting a nested curly brace, error + // e.g. foo/{{bar}_baz} is invalid + // ^ + if (in_open_curly_brace) { + *validation_result = RCL_TOPIC_NAME_INVALID_SUBSTITUTION_CONTAINS_UNALLOWED_CHARACTERS; + if (invalid_index) { + *invalid_index = i; + } + return RCL_RET_OK; + } + in_open_curly_brace = true; + // if it is a new, open curly brace, continue + continue; + } else if (topic_name[i] == '}') { + // if not preceded by a {, error + if (!in_open_curly_brace) { + *validation_result = RCL_TOPIC_NAME_INVALID_UNMATCHED_CURLY_BRACE; + if (invalid_index) { + *invalid_index = i; + } + return RCL_RET_OK; + } + in_open_curly_brace = false; + // if it is a closing curly brace, continue + continue; + } else { + // if it is none of these, then it is an unallowed character in a topic name + if (in_open_curly_brace) { + *validation_result = RCL_TOPIC_NAME_INVALID_SUBSTITUTION_CONTAINS_UNALLOWED_CHARACTERS; + } else { + *validation_result = RCL_TOPIC_NAME_INVALID_CONTAINS_UNALLOWED_CHARACTERS; + } + if (invalid_index) { + *invalid_index = i; + } + return RCL_RET_OK; + } + } + // check to make sure substitutions were properly closed + if (in_open_curly_brace) { + // case where a substitution is never closed, e.g. 'foo/{bar' + *validation_result = RCL_TOPIC_NAME_INVALID_UNMATCHED_CURLY_BRACE; + if (invalid_index) { + *invalid_index = opening_curly_brace_index; + } + return RCL_RET_OK; + } + // check for tokens (other than the first) that start with a number + for (size_t i = 0; i < topic_name_length; ++i) { + if (i == topic_name_length - 1) { + // if this is the last character, then nothing to check + continue; + } + // past this point, assuming i+1 is a valid index + if (topic_name[i] == '/') { + if (isdigit(topic_name[i + 1]) != 0) { + // this is the case where a '/' if followed by a number, i.e. [0-9] + *validation_result = RCL_TOPIC_NAME_INVALID_NAME_TOKEN_STARTS_WITH_NUMBER; + if (invalid_index) { + *invalid_index = i + 1; + } + return RCL_RET_OK; + } + } else if (i == 1 && topic_name[0] == '~') { + // special case where first character is ~ but second character is not / + // e.g. ~foo is invalid + *validation_result = RCL_TOPIC_NAME_INVALID_TILDE_NOT_FOLLOWED_BY_FORWARD_SLASH; + if (invalid_index) { + *invalid_index = 1; + } + return RCL_RET_OK; + } + } + // everything was ok, set result to valid topic, avoid setting invalid_index, and return + *validation_result = RCL_TOPIC_NAME_VALID; + return RCL_RET_OK; +} + +const char * +rcl_topic_name_validation_result_string(int validation_result) +{ + switch (validation_result) { + case RCL_TOPIC_NAME_INVALID_IS_EMPTY_STRING: + return "topic name must not be empty string"; + case RCL_TOPIC_NAME_INVALID_ENDS_WITH_FORWARD_SLASH: + return "topic name must not end with a forward slash"; + case RCL_TOPIC_NAME_INVALID_CONTAINS_UNALLOWED_CHARACTERS: + return + "topic name must not contain characters other than alphanumerics, '_', '~', '{', or '}'"; + case RCL_TOPIC_NAME_INVALID_NAME_TOKEN_STARTS_WITH_NUMBER: + return "topic name token must not start with a number"; + case RCL_TOPIC_NAME_INVALID_UNMATCHED_CURLY_BRACE: + return "topic name must not have unmatched (unbalanced) curly braces '{}'"; + case RCL_TOPIC_NAME_INVALID_MISPLACED_TILDE: + return "topic name must not have tilde '~' unless it is the first character"; + case RCL_TOPIC_NAME_INVALID_TILDE_NOT_FOLLOWED_BY_FORWARD_SLASH: + return "topic name must not have a tilde '~' that is not followed by a forward slash '/'"; + case RCL_TOPIC_NAME_INVALID_SUBSTITUTION_CONTAINS_UNALLOWED_CHARACTERS: + return "substitution name must not contain characters other than alphanumerics or '_'"; + case RCL_TOPIC_NAME_INVALID_SUBSTITUTION_STARTS_WITH_NUMBER: + return "substitution name must not start with a number"; + default: + return NULL; + } +} + +#if __cplusplus +} +#endif diff --git a/rcl/test/CMakeLists.txt b/rcl/test/CMakeLists.txt index c9b1884..941a144 100644 --- a/rcl/test/CMakeLists.txt +++ b/rcl/test/CMakeLists.txt @@ -179,3 +179,17 @@ macro(connext_workaround target) endmacro() call_for_each_rmw_implementation(test_target) + +rcl_add_custom_gtest(test_validate_topic_name + SRCS rcl/test_validate_topic_name.cpp + ENV ${extra_test_env} + APPEND_LIBRARY_DIRS ${extra_lib_dirs} + LIBRARIES ${PROJECT_NAME} ${extra_test_libraries} +) + +rcl_add_custom_gtest(test_expand_topic_name + SRCS rcl/test_expand_topic_name.cpp + ENV ${extra_test_env} + APPEND_LIBRARY_DIRS ${extra_lib_dirs} + LIBRARIES ${PROJECT_NAME} ${extra_test_libraries} +) diff --git a/rcl/test/rcl/test_expand_topic_name.cpp b/rcl/test/rcl/test_expand_topic_name.cpp new file mode 100644 index 0000000..782d1d2 --- /dev/null +++ b/rcl/test/rcl/test_expand_topic_name.cpp @@ -0,0 +1,222 @@ +// Copyright 2017 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 +#include +#include +#include + +#include "rcl/expand_topic_name.h" + +#include "rcl/error_handling.h" + +using namespace std::string_literals; + +TEST(test_expand_topic_name, normal) { + rcl_ret_t ret; + rcl_allocator_t allocator = rcl_get_default_allocator(); + rcutils_string_map_t subs = rcutils_get_zero_initialized_string_map(); + rcutils_ret_t rcu_ret = rcutils_string_map_init(&subs, 0, allocator); + ASSERT_EQ(RCUTILS_RET_OK, rcu_ret); + ret = rcl_get_default_topic_name_substitutions(&subs); + ASSERT_EQ(RCL_RET_OK, ret); + + // {node}/chatter example + { + const char * topic = "{node}/chatter"; + const char * ns = "/my_ns"; + const char * node = "my_node"; + std::string expected = std::string(ns) + "/" + node + "/chatter"; + char * expanded_topic; + ret = rcl_expand_topic_name(topic, node, ns, &subs, allocator, &expanded_topic); + EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe(); + EXPECT_STREQ(expected.c_str(), expanded_topic); + } +} + +TEST(test_expand_topic_name, invalid_arguments) { + rcl_ret_t ret; + rcl_allocator_t allocator = rcl_get_default_allocator(); + rcutils_string_map_t subs = rcutils_get_zero_initialized_string_map(); + rcutils_ret_t rcu_ret = rcutils_string_map_init(&subs, 0, allocator); + ASSERT_EQ(RCUTILS_RET_OK, rcu_ret); + ret = rcl_get_default_topic_name_substitutions(&subs); + ASSERT_EQ(RCL_RET_OK, ret); + + const char * topic = "{node}/chatter"; + const char * ns = "/my_ns"; + const char * node = "my_node"; + char * expanded_topic; + + // pass null for topic string + { + ret = rcl_expand_topic_name(NULL, node, ns, &subs, allocator, &expanded_topic); + EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret); + rcl_reset_error(); + } + + // pass null for node name + { + ret = rcl_expand_topic_name(topic, NULL, ns, &subs, allocator, &expanded_topic); + EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret); + rcl_reset_error(); + } + + // pass null for node namespace + { + ret = rcl_expand_topic_name(topic, node, NULL, &subs, allocator, &expanded_topic); + EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret); + rcl_reset_error(); + } + + // pass null for subs + { + ret = rcl_expand_topic_name(topic, node, ns, NULL, allocator, &expanded_topic); + EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret); + rcl_reset_error(); + } + + // pass null for expanded_topic + { + ret = rcl_expand_topic_name(topic, node, ns, &subs, allocator, NULL); + EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret); + rcl_reset_error(); + } + + // pass invalid topic + { + ret = rcl_expand_topic_name("white space", node, ns, &subs, allocator, &expanded_topic); + EXPECT_EQ(RCL_RET_TOPIC_NAME_INVALID, ret); + rcl_reset_error(); + } + + // pass invalid node name + { + ret = rcl_expand_topic_name(topic, "/invalid_node", ns, &subs, allocator, &expanded_topic); + EXPECT_EQ(RCL_RET_NODE_INVALID_NAME, ret); + rcl_reset_error(); + } + + // pass invalid node namespace + { + ret = rcl_expand_topic_name(topic, node, "white space", &subs, allocator, &expanded_topic); + EXPECT_EQ(RCL_RET_NODE_INVALID_NAMESPACE, ret); + rcl_reset_error(); + } +} + +TEST(test_expand_topic_name, various_valid_topics) { + rcl_ret_t ret; + rcl_allocator_t allocator = rcl_get_default_allocator(); + rcutils_string_map_t subs = rcutils_get_zero_initialized_string_map(); + rcutils_ret_t rcu_ret = rcutils_string_map_init(&subs, 0, allocator); + ASSERT_EQ(RCUTILS_RET_OK, rcu_ret); + ret = rcl_get_default_topic_name_substitutions(&subs); + ASSERT_EQ(RCL_RET_OK, ret); + + std::vector> topics_that_should_expand_to = { + // {"input_topic", "node_name", "/namespace", "expected result"}, + {"/chatter", "my_node", "/my_ns", "/chatter"}, + {"chatter", "my_node", "/my_ns", "/my_ns/chatter"}, + {"{node}/chatter", "my_node", "/my_ns", "/my_ns/my_node/chatter"}, + {"/{node}", "my_node", "/my_ns", "/my_node"}, + {"{node}", "my_node", "/my_ns", "/my_ns/my_node"}, + {"{ns}", "my_node", "/my_ns", "/my_ns"}, + {"{namespace}", "my_node", "/my_ns", "/my_ns"}, + {"{namespace}/{node}/chatter", "my_node", "/my_ns", "/my_ns/my_node/chatter"}, + + // this one will produce an invalid topic, but will pass + // the '//' should be caught by the rmw_validate_full_topic_name() function + {"/foo/{namespace}", "my_node", "/my_ns", "/foo//my_ns"}, + + // examples from the design doc: + // http://design.ros2.org/articles/topic_and_service_names.html + // the node constructor would make the "" namespace into "/" implicitly + {"ping", "my_node", "/", "/ping"}, + {"ping", "my_node", "/my_ns", "/my_ns/ping"}, + + {"/ping", "my_node", "/", "/ping"}, + {"/ping", "my_node", "/my_ns", "/ping"}, + + {"~", "my_node", "/", "/my_node"}, + {"~", "my_node", "/my_ns", "/my_ns/my_node"}, + + {"~/ping", "my_node", "/", "/my_node/ping"}, + {"~/ping", "my_node", "/my_ns", "/my_ns/my_node/ping"}, + }; + + for (const auto & inout : topics_that_should_expand_to) { + const char * topic = inout.at(0).c_str(); + const char * node = inout.at(1).c_str(); + const char * ns = inout.at(2).c_str(); + std::string expected = inout.at(3); + char * expanded_topic; + ret = rcl_expand_topic_name(topic, node, ns, &subs, allocator, &expanded_topic); + std::stringstream ss; + ss << + "^ While expanding '" << topic << + "' with node '" << node << + "' and namespace '" << ns << "'"; + EXPECT_EQ(RCL_RET_OK, ret) << + ss.str() << + ", it failed with '" << ret << "': " << rcl_get_error_string_safe(); + EXPECT_STREQ(expected.c_str(), expanded_topic) << ss.str() << " strings did not match.\n"; + } +} + +TEST(test_expand_topic_name, unknown_substitution) { + rcl_ret_t ret; + rcl_allocator_t allocator = rcl_get_default_allocator(); + rcutils_string_map_t subs = rcutils_get_zero_initialized_string_map(); + rcutils_ret_t rcu_ret = rcutils_string_map_init(&subs, 0, allocator); + ASSERT_EQ(RCUTILS_RET_OK, rcu_ret); + ret = rcl_get_default_topic_name_substitutions(&subs); + ASSERT_EQ(RCL_RET_OK, ret); + + { + const char * topic = "{doesnotexist}"; + const char * ns = "/my_ns"; + const char * node = "my_node"; + char * expanded_topic; + ret = rcl_expand_topic_name(topic, node, ns, &subs, allocator, &expanded_topic); + EXPECT_EQ(RCL_RET_UNKNOWN_SUBSTITUTION, ret); + rcl_reset_error(); + EXPECT_EQ(NULL, expanded_topic); + } +} + +TEST(test_expand_topic_name, custom_substitution) { + rcl_ret_t ret; + rcl_allocator_t allocator = rcl_get_default_allocator(); + rcutils_string_map_t subs = rcutils_get_zero_initialized_string_map(); + rcutils_ret_t rcu_ret = rcutils_string_map_init(&subs, 0, allocator); + ASSERT_EQ(RCUTILS_RET_OK, rcu_ret); + ret = rcl_get_default_topic_name_substitutions(&subs); + ASSERT_EQ(RCL_RET_OK, ret); + + rcu_ret = rcutils_string_map_set(&subs, "ping", "pong"); + ASSERT_EQ(RCUTILS_RET_OK, rcu_ret); + + { + const char * topic = "{ping}"; + const char * ns = "/my_ns"; + const char * node = "my_node"; + char * expanded_topic; + ret = rcl_expand_topic_name(topic, node, ns, &subs, allocator, &expanded_topic); + EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe(); + EXPECT_STREQ("/my_ns/pong", expanded_topic); + } +} diff --git a/rcl/test/rcl/test_validate_topic_name.cpp b/rcl/test/rcl/test_validate_topic_name.cpp new file mode 100644 index 0000000..31b11f4 --- /dev/null +++ b/rcl/test/rcl/test_validate_topic_name.cpp @@ -0,0 +1,174 @@ +// Copyright 2017 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 +#include +#include + +#include "rcl/validate_topic_name.h" + +#include "rcl/error_handling.h" + +TEST(test_validate_topic_name, normal) { + rcl_ret_t ret; + + // passing without invalid_index + { + int validation_result; + ret = rcl_validate_topic_name("topic", &validation_result, NULL); + EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe(); + EXPECT_EQ(RCL_TOPIC_NAME_VALID, validation_result); + EXPECT_EQ(NULL, rcl_topic_name_validation_result_string(validation_result)); + } + + // passing with invalid_index + { + int validation_result; + size_t invalid_index = 42; + ret = rcl_validate_topic_name("topic", &validation_result, &invalid_index); + EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe(); + EXPECT_EQ(RCL_TOPIC_NAME_VALID, validation_result); + EXPECT_EQ(42u, invalid_index); // ensure invalid_index is not assigned on success + EXPECT_EQ(NULL, rcl_topic_name_validation_result_string(validation_result)); + } + + // failing with invalid_index + { + int validation_result; + size_t invalid_index = 42; + ret = rcl_validate_topic_name("", &validation_result, &invalid_index); + EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe(); + EXPECT_EQ(RCL_TOPIC_NAME_INVALID_IS_EMPTY_STRING, validation_result); + EXPECT_EQ(0u, invalid_index); + EXPECT_NE(nullptr, rcl_topic_name_validation_result_string(validation_result)); + } +} + +TEST(test_validate_topic_name, invalid_arguments) { + rcl_ret_t ret; + + // pass null for topic string + { + int validation_result; + ret = rcl_validate_topic_name(NULL, &validation_result, NULL); + EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret); + rcl_reset_error(); + } + + // pass null for validation_result + { + ret = rcl_validate_topic_name("topic", NULL, NULL); + EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret); + rcl_reset_error(); + } +} + +TEST(test_validate_topic_name, various_valid_topics) { + std::vector topics_that_should_pass = { + // examples from the design doc: + // http://design.ros2.org/articles/topic_and_service_names.html#ros-2-name-examples + "foo", + "abc123", + "_foo", + "Foo", + "BAR", + "~", + "foo/bar", + "~/foo", + "{foo}_bar", + "foo/{ping}/bar", + "foo/_bar", + "foo_/bar", + "foo_", + // these two are skipped because their prefixes should be removed before this is called + // "rosservice:///foo", + // "rostopic://foo/bar", + "/foo", + "/bar/baz", + // same reason as above, URL should have been removed already + // "rostopic:///ping", + "/_private/thing", + "/public_namespace/_private/thing", + // these are further corner cases identified: + "/foo", + "{foo1}", + "{foo_bar}", + "{_bar}", + }; + for (const auto & topic : topics_that_should_pass) { + int validation_result; + size_t invalid_index = 42; + rcl_ret_t ret = rcl_validate_topic_name(topic.c_str(), &validation_result, &invalid_index); + EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe(); + EXPECT_EQ(RCL_TOPIC_NAME_VALID, validation_result) << + "'" << topic << "' should have passed: " << + rcl_topic_name_validation_result_string(validation_result) << "\n" << + " " << std::string(invalid_index, ' ') << "^"; + EXPECT_EQ(42u, invalid_index); + EXPECT_STREQ(nullptr, rcl_topic_name_validation_result_string(validation_result)); + } +} + +TEST(test_validate_topic_name, various_invalid_topics) { + struct topic_case + { + std::string topic; + int expected_validation_result; + size_t expected_invalid_index; + }; + std::vector topic_cases_that_should_fail = { + // examples from the design doc: + // http://design.ros2.org/articles/topic_and_service_names.html#ros-2-name-examples + {"123abc", RCL_TOPIC_NAME_INVALID_NAME_TOKEN_STARTS_WITH_NUMBER, 0}, + {"123", RCL_TOPIC_NAME_INVALID_NAME_TOKEN_STARTS_WITH_NUMBER, 0}, + {" ", RCL_TOPIC_NAME_INVALID_CONTAINS_UNALLOWED_CHARACTERS, 0}, + {"foo bar", RCL_TOPIC_NAME_INVALID_CONTAINS_UNALLOWED_CHARACTERS, 3}, + // this one is skipped because it tested later, after expansion + // "foo//bar", + {"/~", RCL_TOPIC_NAME_INVALID_MISPLACED_TILDE, 1}, + {"~foo", RCL_TOPIC_NAME_INVALID_TILDE_NOT_FOLLOWED_BY_FORWARD_SLASH, 1}, + {"foo~", RCL_TOPIC_NAME_INVALID_MISPLACED_TILDE, 3}, + {"foo~/bar", RCL_TOPIC_NAME_INVALID_MISPLACED_TILDE, 3}, + {"foo/~bar", RCL_TOPIC_NAME_INVALID_MISPLACED_TILDE, 4}, + {"foo/~/bar", RCL_TOPIC_NAME_INVALID_MISPLACED_TILDE, 4}, + {"foo/", RCL_TOPIC_NAME_INVALID_ENDS_WITH_FORWARD_SLASH, 3}, + // these are further corner cases identified: + {"", RCL_TOPIC_NAME_INVALID_IS_EMPTY_STRING, 0}, + {"foo/123bar", RCL_TOPIC_NAME_INVALID_NAME_TOKEN_STARTS_WITH_NUMBER, 4}, + {"foo/bar}/baz", RCL_TOPIC_NAME_INVALID_UNMATCHED_CURLY_BRACE, 7}, + {"foo/{bar", RCL_TOPIC_NAME_INVALID_UNMATCHED_CURLY_BRACE, 4}, + {"{$}", RCL_TOPIC_NAME_INVALID_SUBSTITUTION_CONTAINS_UNALLOWED_CHARACTERS, 1}, + {"{{bar}_baz}", RCL_TOPIC_NAME_INVALID_SUBSTITUTION_CONTAINS_UNALLOWED_CHARACTERS, 1}, + {"foo/{bar/baz}", RCL_TOPIC_NAME_INVALID_SUBSTITUTION_CONTAINS_UNALLOWED_CHARACTERS, 8}, + {"{1foo}", RCL_TOPIC_NAME_INVALID_SUBSTITUTION_STARTS_WITH_NUMBER, 1}, + }; + for (const auto & case_tuple : topic_cases_that_should_fail) { + std::string topic = case_tuple.topic; + int expected_validation_result = case_tuple.expected_validation_result; + size_t expected_invalid_index = case_tuple.expected_invalid_index; + int validation_result; + size_t invalid_index = 0; + rcl_ret_t ret = rcl_validate_topic_name(topic.c_str(), &validation_result, &invalid_index); + EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe(); + EXPECT_EQ(expected_validation_result, validation_result) << + "'" << topic << "' should have failed with '" << + expected_validation_result << "' but got '" << validation_result << "'.\n" << + " " << std::string(invalid_index, ' ') << "^"; + EXPECT_EQ(expected_invalid_index, invalid_index) << + "Topic '" << topic << "' failed with '" << validation_result << "'."; + EXPECT_NE(nullptr, rcl_topic_name_validation_result_string(validation_result)) << topic; + } +} diff --git a/rcl_lifecycle/src/com_interface.c b/rcl_lifecycle/src/com_interface.c index 7cd74cf..b3a2a61 100644 --- a/rcl_lifecycle/src/com_interface.c +++ b/rcl_lifecycle/src/com_interface.c @@ -30,7 +30,7 @@ extern "C" #include "rcl_lifecycle/data_types.h" -#include "rmw/validate_topic_name.h" +#include "rmw/validate_full_topic_name.h" #include "rosidl_generator_c/message_type_support_struct.h" #include "rosidl_generator_c/string_functions.h" @@ -48,7 +48,7 @@ rcl_lifecycle_validate_topic_name(const char * topic_name) static rmw_ret_t ret = RMW_RET_ERROR; static int validation_result = RMW_TOPIC_INVALID_IS_EMPTY_STRING; - ret = rmw_validate_topic_name(topic_name, &validation_result, NULL); + ret = rmw_validate_full_topic_name(topic_name, &validation_result, NULL); if (ret != RMW_RET_OK) { RCL_SET_ERROR_MSG("unable to validate topic name", rcl_get_default_allocator()); return RMW_RET_ERROR; @@ -56,7 +56,7 @@ rcl_lifecycle_validate_topic_name(const char * topic_name) // TODO(karsten1987): Handle absolute case if (validation_result != RMW_TOPIC_VALID && validation_result != RMW_TOPIC_INVALID_NOT_ABSOLUTE) { RCL_SET_ERROR_MSG( - rmw_topic_validation_result_string(validation_result), rcl_get_default_allocator()); + rmw_full_topic_name_validation_result_string(validation_result), rcl_get_default_allocator()) return RMW_RET_ERROR; } return RMW_RET_OK;