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:
parent
fc75c58962
commit
cbfe6b90f9
11 changed files with 1140 additions and 4 deletions
|
@ -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
|
||||
)
|
||||
|
||||
|
|
134
rcl/include/rcl/expand_topic_name.h
Normal file
134
rcl/include/rcl/expand_topic_name.h
Normal 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_
|
|
@ -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.
|
||||
|
|
107
rcl/include/rcl/validate_topic_name.h
Normal file
107
rcl/include/rcl/validate_topic_name.h
Normal 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_
|
258
rcl/src/rcl/expand_topic_name.c
Normal file
258
rcl/src/rcl/expand_topic_name.c
Normal 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
|
|
@ -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"
|
||||
|
||||
|
|
222
rcl/src/rcl/validate_topic_name.c
Normal file
222
rcl/src/rcl/validate_topic_name.c
Normal 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
|
|
@ -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}
|
||||
)
|
||||
|
|
222
rcl/test/rcl/test_expand_topic_name.cpp
Normal file
222
rcl/test/rcl/test_expand_topic_name.cpp
Normal 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);
|
||||
}
|
||||
}
|
174
rcl/test/rcl/test_validate_topic_name.cpp
Normal file
174
rcl/test/rcl/test_validate_topic_name.cpp
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue