diff --git a/rmw/CMakeLists.txt b/rmw/CMakeLists.txt index a9c7af3b..792d8138 100644 --- a/rmw/CMakeLists.txt +++ b/rmw/CMakeLists.txt @@ -10,6 +10,7 @@ include_directories(include) add_library(${PROJECT_NAME} SHARED "src/allocators.c" "src/error_handling.c" + "src/validate_node_name.c" "src/validate_topic_name.c" "src/sanity_checks.c") configure_rmw_library(${PROJECT_NAME}) diff --git a/rmw/include/rmw/validate_node_name.h b/rmw/include/rmw/validate_node_name.h new file mode 100644 index 00000000..39d045ca --- /dev/null +++ b/rmw/include/rmw/validate_node_name.h @@ -0,0 +1,95 @@ +// 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 RMW__VALIDATE_NODE_NAME_H_ +#define RMW__VALIDATE_NODE_NAME_H_ + +#if __cplusplus +extern "C" +{ +#endif + +#include "rmw/macros.h" +#include "rmw/types.h" + +#define RMW_NODE_NAME_VALID 0 +#define RMW_NODE_NAME_INVALID_IS_EMPTY_STRING 1 +#define RMW_NODE_NAME_INVALID_CONTAINS_UNALLOWED_CHARACTERS 2 +#define RMW_NODE_NAME_INVALID_STARTS_WITH_NUMBER 3 +#define RMW_NODE_NAME_INVALID_TOO_LONG 4 + +#define RMW_NODE_NAME_MAX_NAME_LENGTH 255 /* arbitrary constraint */ + +/// Determine if a node name is valid. +/** + * Node names must follow these rules: + * + * - must not be an empty string + * - must only contain alphanumeric characters and underscores (a-z|A-Z|0-9|_) + * - must not start with an number + * + * If either the node name C string or validation_result pointer are null, then + * `RMW_RET_INVALID_ARGUMENT` will be returned. + * The node_name should be a valid, null-terminated C string. + * The validation_result int pointer should point to valid memory so a result + * can be stored in it as an output variable. + * The invalid_index size_t pointer should point to valid memory so in the + * event of a validation error, the location in the input string can be stored + * therein. + * + * The invalid_index will not be assigned a value if the node name is valid. + * + * The int which validation_result points to will have a one of a few possible + * results values (defined with macros) stored into it: + * + * - RMW_NODE_NAME_VALID + * - RMW_NODE_NAME_INVALID_IS_EMPTY_STRING + * - RMW_NODE_NAME_INVALID_CONTAINS_UNALLOWED_CHARACTERS + * - RMW_NODE_NAME_INVALID_STARTS_WITH_NUMBER + * - RMW_NODE_NAME_INVALID_TOO_LONG + * + * The result value can be converted to a description with the + * rmw_node_name_validation_result_string() function. + * + * The `RMW_NODE_NAME_INVALID_TOO_LONG` is guaranteed to be checked last, such + * that if you get that result, then you can assume all other checks succeeded. + * This is done so that the length limit can be treated as a warning rather + * than an error if desired. + * + * \param[in] node_name node name to be validated + * \param[out] validation_result int in which the result of the check is stored + * \param[out] invalid_index size_t index of the input string where an error occurred + * \returns `RMW_RET_OK` on successfully running the check, or + * \returns `RMW_RET_INVALID_ARGUMENT` on invalid parameters, or + * \returns `RMW_RET_ERROR` when an unspecified error occurs. + */ +RMW_PUBLIC +RMW_WARN_UNUSED +rmw_ret_t +rmw_validate_node_name( + const char * node_name, + int * validation_result, + size_t * invalid_index); + +/// Return a validation result description, or NULL if unknown or RMW_NODE_NAME_VALID. +RMW_PUBLIC +RMW_WARN_UNUSED +const char * +rmw_node_name_validation_result_string(int validation_result); + +#if __cplusplus +} +#endif + +#endif // RMW__VALIDATE_NODE_NAME_H_ diff --git a/rmw/include/rmw/validate_topic_name.h b/rmw/include/rmw/validate_topic_name.h index 2ef4eb37..c953a3c6 100644 --- a/rmw/include/rmw/validate_topic_name.h +++ b/rmw/include/rmw/validate_topic_name.h @@ -66,7 +66,7 @@ extern "C" * - RMW_TOPIC_INVALID_TOO_LONG * * The result value can be converted to a description with the - * rmw_validation_result_string() function. + * rmw_topic_validation_result_string() function. * * The `RMW_TOPIC_INVALID_TOO_LONG` is guaranteed to be checked last, such * that if you get that result, then you can assume all other checks succeeded. @@ -88,11 +88,11 @@ rmw_validate_topic_name( int * validation_result, size_t * invalid_index); -/// Return a string to describe the validation result, or NULL if unknown. +/// Return a validation result description, or NULL if unknown or RMW_TOPIC_VALID. RMW_PUBLIC RMW_WARN_UNUSED const char * -rmw_validation_result_string(int validation_result); +rmw_topic_validation_result_string(int validation_result); #if __cplusplus } diff --git a/rmw/src/validate_node_name.c b/rmw/src/validate_node_name.c new file mode 100644 index 00000000..b15a5a38 --- /dev/null +++ b/rmw/src/validate_node_name.c @@ -0,0 +1,93 @@ +// Copyright 2017 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include + +#include "./isalnum_no_locale.h" + +rmw_ret_t +rmw_validate_node_name( + const char * node_name, + int * validation_result, + size_t * invalid_index) +{ + if (!node_name) { + return RMW_RET_INVALID_ARGUMENT; + } + if (!validation_result) { + return RMW_RET_INVALID_ARGUMENT; + } + if (!invalid_index) { + return RMW_RET_INVALID_ARGUMENT; + } + size_t node_name_length = strlen(node_name); + if (node_name_length == 0) { + *validation_result = RMW_NODE_NAME_INVALID_IS_EMPTY_STRING; + *invalid_index = 0; + return RMW_RET_OK; + } + // check for unallowed characters + for (size_t i = 0; i < node_name_length; ++i) { + if (isalnum_no_locale(node_name[i])) { + // if it is an alpha numeric character, i.e. [0-9|A-Z|a-z], continue + continue; + } else if (node_name[i] == '_') { + // if it is an underscore, continue + continue; + } else { + // if it is none of these, then it is an unallowed character in a node name + *validation_result = RMW_NODE_NAME_INVALID_CONTAINS_UNALLOWED_CHARACTERS; + *invalid_index = i; + return RMW_RET_OK; + } + } + if (isdigit(node_name[0]) != 0) { + // this is the case where the name starts with a number, i.e. [0-9] + *validation_result = RMW_NODE_NAME_INVALID_STARTS_WITH_NUMBER; + *invalid_index = 0; + return RMW_RET_OK; + } + // check if the node name is too long last, since it might be a soft invalidation + if (node_name_length > RMW_NODE_NAME_MAX_NAME_LENGTH) { + *validation_result = RMW_NODE_NAME_INVALID_TOO_LONG; + *invalid_index = RMW_NODE_NAME_MAX_NAME_LENGTH - 1; + return RMW_RET_OK; + } + // everything was ok, set result to valid node name, avoid setting invalid_index, and return + *validation_result = RMW_NODE_NAME_VALID; + return RMW_RET_OK; +} + +const char * +rmw_node_name_validation_result_string(int validation_result) +{ + switch (validation_result) { + case RMW_NODE_NAME_VALID: + return NULL; + case RMW_NODE_NAME_INVALID_IS_EMPTY_STRING: + return "node name must not be empty"; + case RMW_NODE_NAME_INVALID_CONTAINS_UNALLOWED_CHARACTERS: + return "node name must not contain characters other than alphanumerics or '_'"; + case RMW_NODE_NAME_INVALID_STARTS_WITH_NUMBER: + return "node name must not start with a number"; + case RMW_NODE_NAME_INVALID_TOO_LONG: + return + "node name length should not exceed '" RMW_STRINGIFY(RMW_NODE_NAME_MAX_NAME_LENGTH) "'"; + default: + return NULL; + } +} diff --git a/rmw/src/validate_topic_name.c b/rmw/src/validate_topic_name.c index 7fdb6109..69ff18ed 100644 --- a/rmw/src/validate_topic_name.c +++ b/rmw/src/validate_topic_name.c @@ -103,11 +103,11 @@ rmw_validate_topic_name( } const char * -rmw_validation_result_string(int validation_result) +rmw_topic_validation_result_string(int validation_result) { switch (validation_result) { case RMW_TOPIC_VALID: - return "topic name is valid"; + return NULL; case RMW_TOPIC_INVALID_IS_EMPTY_STRING: return "topic name must not be empty"; case RMW_TOPIC_INVALID_NOT_ABSOLUTE: diff --git a/rmw/test/CMakeLists.txt b/rmw/test/CMakeLists.txt index 064ddfdf..41ecb3db 100644 --- a/rmw/test/CMakeLists.txt +++ b/rmw/test/CMakeLists.txt @@ -46,3 +46,18 @@ if(TARGET test_validate_topic_name) set_target_properties(test_validate_topic_name PROPERTIES COMPILE_FLAGS "-std=c++14") endif() endif() + +ament_add_gmock(test_validate_node_name + test_validate_node_name.cpp + # Append the directory of librmw so it is found at test time. + APPEND_LIBRARY_DIRS "$" +) +if(TARGET test_validate_node_name) + target_link_libraries(test_validate_node_name ${PROJECT_NAME}) + if(UNIX AND NOT APPLE) + target_link_libraries(test_validate_node_name pthread) + endif() + if(NOT WIN32) + set_target_properties(test_validate_node_name PROPERTIES COMPILE_FLAGS "-std=c++14") + endif() +endif() diff --git a/rmw/test/test_validate_node_name.cpp b/rmw/test/test_validate_node_name.cpp new file mode 100644 index 00000000..f14f841c --- /dev/null +++ b/rmw/test/test_validate_node_name.cpp @@ -0,0 +1,130 @@ +// Copyright 2017 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "gmock/gmock.h" + +#include "rmw/validate_node_name.h" + +TEST(test_validate_node_name, invalid_parameters) { + int validation_result; + size_t invalid_index; + rmw_ret_t ret = rmw_validate_node_name(nullptr, &validation_result, &invalid_index); + ASSERT_EQ(RMW_RET_INVALID_ARGUMENT, ret); + ret = rmw_validate_node_name("test", nullptr, &invalid_index); + ASSERT_EQ(RMW_RET_INVALID_ARGUMENT, ret); + ret = rmw_validate_node_name("test", &validation_result, nullptr); + ASSERT_EQ(RMW_RET_INVALID_ARGUMENT, ret); +} + +TEST(test_validate_node_name, valid_node_name) { + int validation_result; + size_t invalid_index; + rmw_ret_t ret; + + ret = rmw_validate_node_name("nodename", &validation_result, &invalid_index); + ASSERT_EQ(RMW_RET_OK, ret); + ASSERT_EQ(RMW_NODE_NAME_VALID, validation_result); + + validation_result = -1; + ret = rmw_validate_node_name("node_name", &validation_result, &invalid_index); + ASSERT_EQ(RMW_RET_OK, ret); + ASSERT_EQ(RMW_NODE_NAME_VALID, validation_result); + + ASSERT_EQ((char *)NULL, rmw_node_name_validation_result_string(validation_result)); +} + +TEST(test_validate_node_name, empty_node_name) { + int validation_result; + size_t invalid_index; + rmw_ret_t ret; + + ret = rmw_validate_node_name("", &validation_result, &invalid_index); + ASSERT_EQ(RMW_RET_OK, ret); + ASSERT_EQ(RMW_NODE_NAME_INVALID_IS_EMPTY_STRING, validation_result); + ASSERT_EQ(0ul, invalid_index); + + ASSERT_NE((char *)NULL, rmw_node_name_validation_result_string(validation_result)); +} + +TEST(test_validate_node_name, unallowed_characters) { + int validation_result; + size_t invalid_index; + rmw_ret_t ret; + + ret = rmw_validate_node_name("node/name", &validation_result, &invalid_index); + ASSERT_EQ(RMW_RET_OK, ret); + ASSERT_EQ(RMW_NODE_NAME_INVALID_CONTAINS_UNALLOWED_CHARACTERS, validation_result); + ASSERT_EQ(4ul, invalid_index); + + ret = rmw_validate_node_name("node_{name}", &validation_result, &invalid_index); + ASSERT_EQ(RMW_RET_OK, ret); + ASSERT_EQ(RMW_NODE_NAME_INVALID_CONTAINS_UNALLOWED_CHARACTERS, validation_result); + ASSERT_EQ(5ul, invalid_index); + + ret = rmw_validate_node_name("~node_name", &validation_result, &invalid_index); + ASSERT_EQ(RMW_RET_OK, ret); + ASSERT_EQ(RMW_NODE_NAME_INVALID_CONTAINS_UNALLOWED_CHARACTERS, validation_result); + ASSERT_EQ(0ul, invalid_index); + + ret = rmw_validate_node_name("with spaces", &validation_result, &invalid_index); + ASSERT_EQ(RMW_RET_OK, ret); + ASSERT_EQ(RMW_NODE_NAME_INVALID_CONTAINS_UNALLOWED_CHARACTERS, validation_result); + ASSERT_EQ(4ul, invalid_index); + + ret = rmw_validate_node_name("with.periods", &validation_result, &invalid_index); + ASSERT_EQ(RMW_RET_OK, ret); + ASSERT_EQ(RMW_NODE_NAME_INVALID_CONTAINS_UNALLOWED_CHARACTERS, validation_result); + ASSERT_EQ(4ul, invalid_index); + + ASSERT_NE((char *)NULL, rmw_node_name_validation_result_string(validation_result)); +} + +TEST(test_validate_node_name, starts_with_number) { + int validation_result; + size_t invalid_index; + rmw_ret_t ret; + + ret = rmw_validate_node_name("42node", &validation_result, &invalid_index); + ASSERT_EQ(RMW_RET_OK, ret); + ASSERT_EQ(RMW_NODE_NAME_INVALID_STARTS_WITH_NUMBER, validation_result); + ASSERT_EQ(0ul, invalid_index); + + ASSERT_NE((char *)NULL, rmw_node_name_validation_result_string(validation_result)); +} + +TEST(test_validate_node_name, node_name_too_long) { + int validation_result; + size_t invalid_index; + rmw_ret_t ret; + + // Ensure the length is not the first error + std::string invalid_and_long_node_name(RMW_NODE_NAME_MAX_NAME_LENGTH + 1, '0'); + ret = rmw_validate_node_name( + invalid_and_long_node_name.c_str(), &validation_result, &invalid_index); + ASSERT_EQ(RMW_RET_OK, ret); + ASSERT_EQ(RMW_NODE_NAME_INVALID_STARTS_WITH_NUMBER, validation_result); + ASSERT_EQ(0ul, invalid_index); + + // Ensure length check works when there are no other issues + std::string valid_but_long_node_name(RMW_NODE_NAME_MAX_NAME_LENGTH + 1, 'a'); + ret = rmw_validate_node_name( + valid_but_long_node_name.c_str(), &validation_result, &invalid_index); + ASSERT_EQ(RMW_RET_OK, ret); + EXPECT_EQ(RMW_NODE_NAME_INVALID_TOO_LONG, validation_result); + EXPECT_EQ(RMW_NODE_NAME_MAX_NAME_LENGTH - 1, invalid_index); + + ASSERT_NE((char *)NULL, rmw_node_name_validation_result_string(validation_result)); +} diff --git a/rmw/test/test_validate_topic_name.cpp b/rmw/test/test_validate_topic_name.cpp index 248ec079..0704b87f 100644 --- a/rmw/test/test_validate_topic_name.cpp +++ b/rmw/test/test_validate_topic_name.cpp @@ -43,7 +43,7 @@ TEST(test_validate_topic_name, valid_topic) { ASSERT_EQ(RMW_RET_OK, ret); ASSERT_EQ(RMW_TOPIC_VALID, validation_result); - ASSERT_NE((char *)NULL, rmw_validation_result_string(validation_result)); + ASSERT_EQ((char *)NULL, rmw_topic_validation_result_string(validation_result)); } TEST(test_validate_topic_name, empty_topic_name) { @@ -56,7 +56,7 @@ TEST(test_validate_topic_name, empty_topic_name) { ASSERT_EQ(RMW_TOPIC_INVALID_IS_EMPTY_STRING, validation_result); ASSERT_EQ(0ul, invalid_index); - ASSERT_NE((char *)NULL, rmw_validation_result_string(validation_result)); + ASSERT_NE((char *)NULL, rmw_topic_validation_result_string(validation_result)); } TEST(test_validate_topic_name, not_absolute) { @@ -74,7 +74,7 @@ TEST(test_validate_topic_name, not_absolute) { ASSERT_EQ(RMW_TOPIC_INVALID_NOT_ABSOLUTE, validation_result); ASSERT_EQ(0ul, invalid_index); - ASSERT_NE((char *)NULL, rmw_validation_result_string(validation_result)); + ASSERT_NE((char *)NULL, rmw_topic_validation_result_string(validation_result)); } TEST(test_validate_topic_name, ends_with_forward_slash) { @@ -92,7 +92,7 @@ TEST(test_validate_topic_name, ends_with_forward_slash) { ASSERT_EQ(RMW_TOPIC_INVALID_ENDS_WITH_FORWARD_SLASH, validation_result); ASSERT_EQ(0ul, invalid_index); - ASSERT_NE((char *)NULL, rmw_validation_result_string(validation_result)); + ASSERT_NE((char *)NULL, rmw_topic_validation_result_string(validation_result)); } TEST(test_validate_topic_name, unallowed_characters) { @@ -120,7 +120,7 @@ TEST(test_validate_topic_name, unallowed_characters) { ASSERT_EQ(RMW_TOPIC_INVALID_CONTAINS_UNALLOWED_CHARACTERS, validation_result); ASSERT_EQ(5ul, invalid_index); - ASSERT_NE((char *)NULL, rmw_validation_result_string(validation_result)); + ASSERT_NE((char *)NULL, rmw_topic_validation_result_string(validation_result)); } TEST(test_validate_topic_name, repeated_forward_slashes) { @@ -133,7 +133,7 @@ TEST(test_validate_topic_name, repeated_forward_slashes) { ASSERT_EQ(RMW_TOPIC_INVALID_CONTAINS_REPEATED_FORWARD_SLASH, validation_result); ASSERT_EQ(10ul, invalid_index); - ASSERT_NE((char *)NULL, rmw_validation_result_string(validation_result)); + ASSERT_NE((char *)NULL, rmw_topic_validation_result_string(validation_result)); } TEST(test_validate_topic_name, starts_with_number) { @@ -151,7 +151,7 @@ TEST(test_validate_topic_name, starts_with_number) { ASSERT_EQ(RMW_TOPIC_INVALID_NAME_TOKEN_STARTS_WITH_NUMBER, validation_result); ASSERT_EQ(8ul, invalid_index); - ASSERT_NE((char *)NULL, rmw_validation_result_string(validation_result)); + ASSERT_NE((char *)NULL, rmw_topic_validation_result_string(validation_result)); } TEST(test_validate_topic_name, topic_too_long) { @@ -160,7 +160,7 @@ TEST(test_validate_topic_name, topic_too_long) { rmw_ret_t ret; // Ensure the length is not the first error - std::string invalid_and_long_topic(RMW_TOPIC_MAX_NAME_LENGTH, 'a'); + std::string invalid_and_long_topic(RMW_TOPIC_MAX_NAME_LENGTH + 1, 'a'); ret = rmw_validate_topic_name( invalid_and_long_topic.c_str(), &validation_result, &invalid_index); ASSERT_EQ(RMW_RET_OK, ret); @@ -174,5 +174,5 @@ TEST(test_validate_topic_name, topic_too_long) { EXPECT_EQ(RMW_TOPIC_INVALID_TOO_LONG, validation_result); EXPECT_EQ(RMW_TOPIC_MAX_NAME_LENGTH - 1, invalid_index); - ASSERT_NE((char *)NULL, rmw_validation_result_string(validation_result)); + ASSERT_NE((char *)NULL, rmw_topic_validation_result_string(validation_result)); }