Namespace expansion (#132)

* update to use new rmw function name

* add rcl_validate_topic_name()

* add rcl_expand_topic_name()

* gcc couldn't handle the init lists with tuples

* uncrustify and cpplint

* fix signed compare warnings

* address comments

* organize the substitution string constants together at the top of the file

* comment
This commit is contained in:
William Woodall 2017-05-09 15:15:57 -07:00 committed by GitHub
parent fc75c58962
commit cbfe6b90f9
11 changed files with 1140 additions and 4 deletions

View file

@ -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
)

View file

@ -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.
*
* <hr>
* 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_

View file

@ -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.

View file

@ -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_

View file

@ -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 <stdio.h>
#include <string.h>
#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

View file

@ -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"

View file

@ -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 <ctype.h>
#include <string.h>
#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

View file

@ -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}
)

View file

@ -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 <gtest/gtest.h>
#include <sstream>
#include <string>
#include <tuple>
#include <vector>
#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<std::vector<std::string>> 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);
}
}

View file

@ -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 <gtest/gtest.h>
#include <string>
#include <tuple>
#include <vector>
#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<std::string> 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_case> 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;
}
}

View file

@ -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;