Skip to content

Commit

Permalink
add a function to validate topic names (#92)
Browse files Browse the repository at this point in the history
* add a function to valid topic names

* fix typo in macro name

* link gtest tests against pthread on Linux

* use custom isalnum that isn't affected by locale

* relocate files out of impl folders

* fix includes and remove _impl from names

* refactor macros to all start with RMW_TOPIC_

* make linkage internal for isalnum_no_locale

* cpplint fixup
  • Loading branch information
wjwwood authored Mar 22, 2017
1 parent 4c87763 commit 9dc1f65
Show file tree
Hide file tree
Showing 8 changed files with 523 additions and 0 deletions.
1 change: 1 addition & 0 deletions rmw/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ include_directories(include)
add_library(${PROJECT_NAME} SHARED
"src/allocators.c"
"src/error_handling.c"
"src/validate_topic_name.c"
"src/sanity_checks.c")
configure_rmw_library(${PROJECT_NAME})

Expand Down
5 changes: 5 additions & 0 deletions rmw/include/rmw/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ typedef int rmw_ret_t;
#define RMW_RET_ERROR 1
#define RMW_RET_TIMEOUT 2

/// Failed to allocate memory return code.
#define RMW_RET_BAD_ALLOC 10
/// Invalid argument return code.
#define RMW_RET_INVALID_ARGUMENT 11

// 24 bytes is the most memory needed to represent the GID by any current
// implementation. It may need to be increased in the future.
#define RMW_GID_STORAGE_SIZE 24
Expand Down
101 changes: 101 additions & 0 deletions rmw/include/rmw/validate_topic_name.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// 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_TOPIC_NAME_H_
#define RMW__VALIDATE_TOPIC_NAME_H_

#if __cplusplus
extern "C"
{
#endif

#include "rmw/macros.h"
#include "rmw/types.h"

#define RMW_TOPIC_VALID 0
#define RMW_TOPIC_INVALID_IS_EMPTY_STRING 1
#define RMW_TOPIC_INVALID_NOT_ABSOLUTE 2
#define RMW_TOPIC_INVALID_ENDS_WITH_FORWARD_SLASH 3
#define RMW_TOPIC_INVALID_CONTAINS_UNALLOWED_CHARACTERS 4
#define RMW_TOPIC_INVALID_CONTAINS_REPEATED_FORWARD_SLASH 5
#define RMW_TOPIC_INVALID_NAME_TOKEN_STARTS_WITH_NUMBER 6
#define RMW_TOPIC_INVALID_TOO_LONG 7

#define RMW_TOPIC_MAX_NAME_LENGTH 255 /* impl constraint */ - 8 /* reserved for prefixes */

/// Determine if a given fully qualified topic name is valid.
/** Validity of a FQN for topic is determined based on rules defined here:
*
* 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 either the C string or validation_result pointer are null, then
* `RMW_RET_INVALID_ARGUMENT` will be returned.
* The topic_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 topic 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_VALID_TOPIC
* - RMW_TOPIC_INVALID_IS_EMPTY_STRING
* - RMW_TOPIC_INVALID_NOT_ABSOLUTE
* - RMW_TOPIC_INVALID_ENDS_WITH_FORWARD_SLASH
* - RMW_TOPIC_INVALID_CONTAINS_UNALLOWED_CHARACTERS
* - RMW_TOPIC_INVALID_CONTAINS_REPEATED_FORWARD_SLASH
* - RMW_TOPIC_INVALID_NAME_TOKEN_STARTS_WITH_NUMBER
* - RMW_TOPIC_INVALID_TOO_LONG
*
* The result value can be converted to a description with the
* rmw_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.
* This is done so that the length limit can be treated as a warning rather
* than an error if desired.
*
* \param[in] topic_name topic 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_topic_name(
const char * topic_name,
int * validation_result,
size_t * invalid_index);

/// Return a string to describe the validation result, or NULL if unknown.
RMW_PUBLIC
RMW_WARN_UNUSED
const char *
rmw_validation_result_string(int validation_result);

#if __cplusplus
}
#endif

#endif // RMW__VALIDATE_TOPIC_NAME_H_
47 changes: 47 additions & 0 deletions rmw/src/isalnum_no_locale.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// 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 ISALNUM_NO_LOCALE_H_
#define ISALNUM_NO_LOCALE_H_

#if __cplusplus
extern "C"
{
#endif

/// Custom isalnum which is not affected by locale.
static inline
bool
isalnum_no_locale(char c)
{
// if in '0', ..., '9', then ok
if (c >= 0x30 /*0*/ && c <= 0x39 /*9*/) {
return true;
}
// if in 'A', ..., 'Z', then ok
if (c >= 0x41 /*A*/ && c <= 0x5a /*Z*/) {
return true;
}
// if in 'a', ..., 'z', then ok
if (c >= 0x61 /*a*/ && c <= 0x7a /*z*/) {
return true;
}
return false;
}

#if __cplusplus
}
#endif

#endif // ISALNUM_NO_LOCALE_H_
128 changes: 128 additions & 0 deletions rmw/src/validate_topic_name.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// 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 <rmw/validate_topic_name.h>

#include <ctype.h>
#include <string.h>

#include "./isalnum_no_locale.h"

rmw_ret_t
rmw_validate_topic_name(
const char * topic_name,
int * validation_result,
size_t * invalid_index)
{
if (!topic_name) {
return RMW_RET_INVALID_ARGUMENT;
}
if (!validation_result) {
return RMW_RET_INVALID_ARGUMENT;
}
if (!invalid_index) {
return RMW_RET_INVALID_ARGUMENT;
}
size_t topic_name_length = strlen(topic_name);
if (topic_name_length == 0) {
*validation_result = RMW_TOPIC_INVALID_IS_EMPTY_STRING;
*invalid_index = 0;
return RMW_RET_OK;
}
if (topic_name[0] != '/') {
*validation_result = RMW_TOPIC_INVALID_NOT_ABSOLUTE;
*invalid_index = 0;
return RMW_RET_OK;
}
// note topic_name_length is >= 1 at this point
if (topic_name[topic_name_length - 1] == '/') {
// catches both "/foo/" and "/"
*validation_result = RMW_TOPIC_INVALID_ENDS_WITH_FORWARD_SLASH;
*invalid_index = topic_name_length - 1;
return RMW_RET_OK;
}
// check for unallowed characters
for (size_t i = 0; i < topic_name_length; ++i) {
if (isalnum_no_locale(topic_name[i])) {
// 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, continue
continue;
} else {
// if it is none of these, then it is an unallowed character in a FQN topic name
*validation_result = RMW_TOPIC_INVALID_CONTAINS_UNALLOWED_CHARACTERS;
*invalid_index = i;
return RMW_RET_OK;
}
}
// check for double '/' and tokens 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 (topic_name[i + 1] == '/') {
*validation_result = RMW_TOPIC_INVALID_CONTAINS_REPEATED_FORWARD_SLASH;
*invalid_index = i + 1;
return RMW_RET_OK;
}
if (isdigit(topic_name[i + 1]) != 0) {
// this is the case where a '/' if followed by a number, i.e. [0-9]
*validation_result = RMW_TOPIC_INVALID_NAME_TOKEN_STARTS_WITH_NUMBER;
*invalid_index = i + 1;
return RMW_RET_OK;
}
}
}
// check if the topic name is too long last, since it might be a soft invalidation
if (topic_name_length > RMW_TOPIC_MAX_NAME_LENGTH) {
*validation_result = RMW_TOPIC_INVALID_TOO_LONG;
*invalid_index = RMW_TOPIC_MAX_NAME_LENGTH - 1;
return RMW_RET_OK;
}
// everything was ok, set result to valid topic, avoid setting invalid_index, and return
*validation_result = RMW_TOPIC_VALID;
return RMW_RET_OK;
}

const char *
rmw_validation_result_string(int validation_result)
{
switch (validation_result) {
case RMW_TOPIC_VALID:
return "topic name is valid";
case RMW_TOPIC_INVALID_IS_EMPTY_STRING:
return "topic name must not be empty";
case RMW_TOPIC_INVALID_NOT_ABSOLUTE:
return "topic name must be absolute, it must lead with a '/'";
case RMW_TOPIC_INVALID_ENDS_WITH_FORWARD_SLASH:
return "topic name must not end with a '/'";
case RMW_TOPIC_INVALID_CONTAINS_UNALLOWED_CHARACTERS:
return "topic name must not contain characters other than alphanumerics, '_', or '/'";
case RMW_TOPIC_INVALID_CONTAINS_REPEATED_FORWARD_SLASH:
return "topic name must not contain repeated '/'";
case RMW_TOPIC_INVALID_NAME_TOKEN_STARTS_WITH_NUMBER:
return "topic name must not have a token that starts with a number";
case RMW_TOPIC_INVALID_TOO_LONG:
return "topic length should not exceed '" RMW_STRINGIFY(RMW_TOPIC_MAX_NAME_LENGTH) "'";
default:
return NULL;
}
}
30 changes: 30 additions & 0 deletions rmw/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,33 @@ if(TARGET test_error_handling)
set_target_properties(test_error_handling PROPERTIES COMPILE_FLAGS "-std=c++14")
endif()
endif()

ament_add_gmock(test_isalnum_no_locale
test_isalnum_no_locale.cpp
# Append the directory of librmw so it is found at test time.
APPEND_LIBRARY_DIRS "$<TARGET_FILE_DIR:${PROJECT_NAME}>"
)
if(TARGET test_isalnum_no_locale)
target_link_libraries(test_isalnum_no_locale ${PROJECT_NAME})
if(UNIX AND NOT APPLE)
target_link_libraries(test_isalnum_no_locale pthread)
endif()
if(NOT WIN32)
set_target_properties(test_isalnum_no_locale PROPERTIES COMPILE_FLAGS "-std=c++14")
endif()
endif()

ament_add_gmock(test_validate_topic_name
test_validate_topic_name.cpp
# Append the directory of librmw so it is found at test time.
APPEND_LIBRARY_DIRS "$<TARGET_FILE_DIR:${PROJECT_NAME}>"
)
if(TARGET test_validate_topic_name)
target_link_libraries(test_validate_topic_name ${PROJECT_NAME})
if(UNIX AND NOT APPLE)
target_link_libraries(test_validate_topic_name pthread)
endif()
if(NOT WIN32)
set_target_properties(test_validate_topic_name PROPERTIES COMPILE_FLAGS "-std=c++14")
endif()
endif()
33 changes: 33 additions & 0 deletions rmw/test/test_isalnum_no_locale.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// 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 <string>

#include "gmock/gmock.h"

#include "../src/isalnum_no_locale.h"

TEST(test_isalnum_no_locale, valid_characters_ok) {
std::string valid("0123456789" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz");
for (auto & c : valid) {
ASSERT_TRUE(isalnum_no_locale(c));
}
}

TEST(test_isalnum_no_locale, invalid_characters_fail) {
std::string invalid("/" /*0-9*/ ":;<=>?@" /*A-Z*/ "[\\]^_`" /*a-z*/);
for (auto & c : invalid) {
ASSERT_FALSE(isalnum_no_locale(c));
}
}
Loading

0 comments on commit 9dc1f65

Please sign in to comment.