diff --git a/rcl/include/rcl/node.h b/rcl/include/rcl/node.h index 2c8a0a4..945f662 100644 --- a/rcl/include/rcl/node.h +++ b/rcl/include/rcl/node.h @@ -44,9 +44,12 @@ typedef struct rcl_node_t typedef struct rcl_node_options_t { // bool anonymous_name; + // rmw_qos_profile_t parameter_qos; + /// If true, no parameter infrastructure will be setup. // bool no_parameters; + /// If set, then this value overrides the ROS_DOMAIN_ID environment variable. /** * It defaults to RCL_NODE_OPTIONS_DEFAULT_DOMAIN_ID, which will cause the @@ -59,6 +62,7 @@ typedef struct rcl_node_options_t * (currently max size_t) */ size_t domain_id; + /// Custom allocator used for internal allocations. rcl_allocator_t allocator; } rcl_node_options_t; @@ -77,11 +81,27 @@ rcl_get_zero_initialized_node(void); * After calling, the ROS node object can be used to create other middleware * primitives like publishers, services, parameters, etc. * + * The name of the node must not be NULL and adhere to naming restrictions, + * see the rmw_validate_node_name() function for rules. + * * \todo TODO(wjwwood): node name uniqueness is no yet enforced * * The name of the node cannot coincide with another node of the same name. * If a node of the same name is already in the domain, it will be shutdown. * + * The namespace of the node should not be NULL and should also pass the + * rmw_validate_namespace() function's rules. + * + * Additionally this function allows namespaces which lack a leading forward + * slash. + * Because there is no notion of a relative namespace, there is no difference + * between a namespace which lacks a forward and the same namespace with a + * leasing forward slash. + * Therefore, a namespace like ``"foo/bar"`` is automatically changed to + * ``"/foo/bar"`` by this function. + * Similarly, the namespace ``""`` will implicitly become ``"/"`` which is a + * valid namespace. + * * \todo TODO(wjwwood): * Parameter infrastructure is currently initialized in the language specific * client library, e.g. rclcpp for C++, but will be initialized here in the @@ -104,7 +124,7 @@ rcl_get_zero_initialized_node(void); * rcl_node_t node = rcl_get_zero_initialized_node(); * rcl_node_options_t * node_ops = rcl_node_get_default_options(); * // ... node options customization - * rcl_ret_t ret = rcl_node_init(&node, "node_name", node_ops); + * rcl_ret_t ret = rcl_node_init(&node, "node_name", "/node_ns", node_ops); * // ... error handling and then use the node, but eventually deinitialize it: * ret = rcl_node_fini(&node); * // ... error handling for rcl_node_fini() @@ -123,18 +143,25 @@ rcl_get_zero_initialized_node(void); * \post the node handle is valid and can be used in other `rcl_*` functions * * \param[inout] node a preallocated rcl_node_t - * \param[in] name the name of the node + * \param[in] name the name of the node, must be a valid c-string + * \param[in] namespace_ the namespace of the node, must be a valid c-string * \param[in] options the node options * \return `RCL_RET_OK` if the node was initialized successfully, or * \return `RCL_RET_ALREADY_INIT` if the node has already be initialized, 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_NODE_INVALID_NAME` if the name is invalid, or + * \return `RCL_RET_NODE_INVALID_NAMESPACE` if the namespace_ is invalid, or * \return `RCL_RET_ERROR` if an unspecified error occurs. */ RCL_PUBLIC RCL_WARN_UNUSED rcl_ret_t -rcl_node_init(rcl_node_t * node, const char * name, const rcl_node_options_t * options); +rcl_node_init( + rcl_node_t * node, + const char * name, + const char * namespace_, + const rcl_node_options_t * options); /// Finalized a rcl_node_t. /** @@ -240,6 +267,33 @@ RCL_WARN_UNUSED const char * rcl_node_get_name(const rcl_node_t * node); +/// Return the namespace of the node. +/** + * This function returns the node's internal namespace string. + * This function can fail, and therefore return `NULL`, if: + * - node is `NULL` + * - node has not been initialized (the implementation is invalid) + * + * The returned string is only valid as long as the given rcl_node_t is valid. + * The value of the string may change if the value in the rcl_node_t changes, + * and therefore copying the string is recommended if this is a concern. + * + *
+ * Attribute | Adherence + * ------------------ | ------------- + * Allocates Memory | No + * Thread-Safe | No + * Uses Atomics | No + * Lock-Free | Yes + * + * \param[in] node pointer to the node + * \return name string if successful, otherwise `NULL` + */ +RCL_PUBLIC +RCL_WARN_UNUSED +const char * +rcl_node_get_namespace(const rcl_node_t * node); + /// Return the rcl node options. /** * This function returns the node's internal options struct. diff --git a/rcl/include/rcl/types.h b/rcl/include/rcl/types.h index bf6bdc4..9062c1b 100644 --- a/rcl/include/rcl/types.h +++ b/rcl/include/rcl/types.h @@ -24,22 +24,24 @@ typedef rmw_ret_t rcl_ret_t; #define RCL_RET_ERROR RMW_RET_ERROR /// Timeout occurred return code. #define RCL_RET_TIMEOUT RMW_RET_TIMEOUT +/// Failed to allocate memory return code. +#define RCL_RET_BAD_ALLOC RMW_RET_BAD_ALLOC +/// Invalid argument return code. +#define RCL_RET_INVALID_ARGUMENT RMW_RET_INVALID_ARGUMENT // rcl specific ret codes start at 100 /// rcl_init() already called return code. #define RCL_RET_ALREADY_INIT 100 /// rcl_init() not yet called return code. #define RCL_RET_NOT_INIT 101 -/// Failed to allocate memory return code. -#define RCL_RET_BAD_ALLOC 102 -/// Invalid argument return code. -#define RCL_RET_INVALID_ARGUMENT 103 /// Mismatched rmw identifier return code. -#define RCL_RET_MISMATCHED_RMW_ID 104 +#define RCL_RET_MISMATCHED_RMW_ID 102 // rcl node specific ret codes in 2XX /// Invalid rcl_node_t given return code. #define RCL_RET_NODE_INVALID 200 +#define RCL_RET_NODE_INVALID_NAME 201 +#define RCL_RET_NODE_INVALID_NAMESPACE 202 // rcl publisher specific ret codes in 3XX /// Invalid rcl_publisher_t given return code. diff --git a/rcl/src/rcl/node.c b/rcl/src/rcl/node.c index e987f0c..aa3d5e2 100644 --- a/rcl/src/rcl/node.c +++ b/rcl/src/rcl/node.c @@ -26,9 +26,19 @@ extern "C" #include "rcl/rcl.h" #include "rmw/rmw.h" +#include "rmw/validate_namespace.h" +#include "rmw/validate_node_name.h" +#include "rmw/validate_topic_name.h" #include "./common.h" +#ifndef _WIN32 + #define LOCAL_SNPRINTF snprintf +#else + #define LOCAL_SNPRINTF(buffer, buffer_size, format, ...) \ + _snprintf_s(buffer, buffer_size, _TRUNCATE, format, __VA_ARGS__) +#endif + typedef struct rcl_node_impl_t { rcl_node_options_t options; @@ -46,7 +56,11 @@ rcl_get_zero_initialized_node() } rcl_ret_t -rcl_node_init(rcl_node_t * node, const char * name, const rcl_node_options_t * options) +rcl_node_init( + rcl_node_t * node, + const char * name, + const char * namespace_, + const rcl_node_options_t * options) { size_t domain_id = 0; const char * ros_domain_id; @@ -56,6 +70,7 @@ rcl_node_init(rcl_node_t * node, const char * name, const rcl_node_options_t * o rcl_ret_t ret; rcl_ret_t fail_ret = RCL_RET_ERROR; RCL_CHECK_ARGUMENT_FOR_NULL(name, RCL_RET_INVALID_ARGUMENT); + RCL_CHECK_ARGUMENT_FOR_NULL(namespace_, RCL_RET_INVALID_ARGUMENT); RCL_CHECK_ARGUMENT_FOR_NULL(options, RCL_RET_INVALID_ARGUMENT); RCL_CHECK_ARGUMENT_FOR_NULL(node, RCL_RET_INVALID_ARGUMENT); if (node->impl) { @@ -72,18 +87,69 @@ rcl_node_init(rcl_node_t * node, const char * name, const rcl_node_options_t * o allocator->allocate, "allocate not set", return RCL_RET_INVALID_ARGUMENT); RCL_CHECK_FOR_NULL_WITH_MSG( allocator->deallocate, "deallocate not set", return RCL_RET_INVALID_ARGUMENT); + // Make sure the node name is valid before allocating memory. + int validation_result = 0; + ret = rmw_validate_node_name(name, &validation_result, NULL); + if (ret != RMW_RET_OK) { + RCL_SET_ERROR_MSG(rmw_get_error_string_safe()); + return ret; + } + if (validation_result != RMW_NODE_NAME_VALID) { + const char * msg = rmw_node_name_validation_result_string(validation_result); + if (!msg) { + msg = "unknown validation_result, this should not happen"; + } + RCL_SET_ERROR_MSG(msg); + return RCL_RET_NODE_INVALID_NAME; + } + + // Process the namespace. + size_t namespace_length = strlen(namespace_); + const char * local_namespace_ = namespace_; + bool should_free_local_namespace_ = false; + // If the namespace is just an empty string, replace with "/" + if (namespace_length == 0) { + // Have this special case to avoid a memory allocation when "" is passed. + local_namespace_ = "/"; + } + // If the namespace does not start with a /, add one. + if (namespace_length > 0 && namespace_[0] != '/') { + // TODO(wjwwood): replace with generic strcat that takes an allocator once available + // length + 2, because new leading / and terminating \0 + char * temp = (char *)allocator->allocate(namespace_length + 2, allocator->state); + RCL_CHECK_FOR_NULL_WITH_MSG(temp, "allocating memory failed", return RCL_RET_BAD_ALLOC); + temp[0] = '/'; + memcpy(temp + 1, namespace_, strlen(namespace_) + 1); + local_namespace_ = temp; + should_free_local_namespace_ = true; + } + // Make sure the node namespace is valid. + validation_result = 0; + ret = rmw_validate_namespace(local_namespace_, &validation_result, NULL); + if (ret != RMW_RET_OK) { + RCL_SET_ERROR_MSG(rmw_get_error_string_safe()); + return ret; + } + if (validation_result != RMW_NAMESPACE_VALID) { + const char * msg = rmw_namespace_validation_result_string(validation_result); + if (!msg) { + char fixed_msg[256]; + LOCAL_SNPRINTF( + fixed_msg, sizeof(fixed_msg), + "unknown validation_result '%d', this should not happen", validation_result); + RCL_SET_ERROR_MSG(fixed_msg); + } else { + RCL_SET_ERROR_MSG(msg); + } + return RCL_RET_NODE_INVALID_NAMESPACE; + } + // Allocate space for the implementation struct. node->impl = (rcl_node_impl_t *)allocator->allocate(sizeof(rcl_node_impl_t), allocator->state); RCL_CHECK_FOR_NULL_WITH_MSG(node->impl, "allocating memory failed", return RCL_RET_BAD_ALLOC); node->impl->rmw_node_handle = NULL; node->impl->graph_guard_condition = NULL; // Initialize node impl. - // node name - size_t name_len = strlen(name); - if (name_len == 0) { - RCL_SET_ERROR_MSG("node name cannot be empty string"); - goto fail; - } // node options (assume it is trivially copyable) node->impl->options = *options; // node rmw_node_handle @@ -106,9 +172,13 @@ rcl_node_init(rcl_node_t * node, const char * name, const rcl_node_options_t * o } // actual domain id node->impl->actual_domain_id = domain_id; - node->impl->rmw_node_handle = rmw_create_node(name, domain_id); + node->impl->rmw_node_handle = rmw_create_node(name, local_namespace_, domain_id); RCL_CHECK_FOR_NULL_WITH_MSG( node->impl->rmw_node_handle, rmw_get_error_string_safe(), goto fail); + // free local_namespace_ if necessary + if (should_free_local_namespace_) { + allocator->deallocate((char *)local_namespace_, allocator->state); + } // instance id node->impl->rcl_instance_id = rcl_get_instance_id(); // graph guard condition @@ -215,6 +285,15 @@ rcl_node_get_name(const rcl_node_t * node) return node->impl->rmw_node_handle->name; } +const char * +rcl_node_get_namespace(const rcl_node_t * node) +{ + if (!rcl_node_is_valid(node)) { + return NULL; + } + return node->impl->rmw_node_handle->namespace_; +} + const rcl_node_options_t * rcl_node_get_options(const rcl_node_t * node) { diff --git a/rcl/test/rcl/client_fixture.cpp b/rcl/test/rcl/client_fixture.cpp index d718565..f43dcad 100644 --- a/rcl/test/rcl/client_fixture.cpp +++ b/rcl/test/rcl/client_fixture.cpp @@ -109,7 +109,7 @@ int main(int argc, char ** argv) rcl_node_t node = rcl_get_zero_initialized_node(); const char * name = "node_name"; rcl_node_options_t node_options = rcl_node_get_default_options(); - if (rcl_node_init(&node, name, &node_options) != RCL_RET_OK) { + if (rcl_node_init(&node, name, "", &node_options) != RCL_RET_OK) { fprintf(stderr, "Error in node init: %s\n", rcl_get_error_string_safe()); return -1; } diff --git a/rcl/test/rcl/service_fixture.cpp b/rcl/test/rcl/service_fixture.cpp index 3fe2baf..0d01fdd 100644 --- a/rcl/test/rcl/service_fixture.cpp +++ b/rcl/test/rcl/service_fixture.cpp @@ -83,7 +83,7 @@ int main(int argc, char ** argv) rcl_node_t node = rcl_get_zero_initialized_node(); const char * name = "node_name"; rcl_node_options_t node_options = rcl_node_get_default_options(); - if (rcl_node_init(&node, name, &node_options) != RCL_RET_OK) { + if (rcl_node_init(&node, name, "", &node_options) != RCL_RET_OK) { fprintf(stderr, "Error in node init: %s\n", rcl_get_error_string_safe()); return -1; } diff --git a/rcl/test/rcl/test_client.cpp b/rcl/test/rcl/test_client.cpp index 643dd93..087d44a 100644 --- a/rcl/test/rcl/test_client.cpp +++ b/rcl/test/rcl/test_client.cpp @@ -39,7 +39,7 @@ public: *this->node_ptr = rcl_get_zero_initialized_node(); const char * name = "node_name"; rcl_node_options_t node_options = rcl_node_get_default_options(); - ret = rcl_node_init(this->node_ptr, name, &node_options); + ret = rcl_node_init(this->node_ptr, name, "", &node_options); ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe(); set_on_unexpected_malloc_callback([]() {ASSERT_FALSE(true) << "UNEXPECTED MALLOC";}); set_on_unexpected_realloc_callback([]() {ASSERT_FALSE(true) << "UNEXPECTED REALLOC";}); diff --git a/rcl/test/rcl/test_get_node_names.cpp b/rcl/test/rcl/test_get_node_names.cpp index 536e14c..463b31f 100644 --- a/rcl/test/rcl/test_get_node_names.cpp +++ b/rcl/test/rcl/test_get_node_names.cpp @@ -63,14 +63,14 @@ TEST_F(CLASSNAME(TestGetNodeNames, RMW_IMPLEMENTATION), test_rcl_get_node_names) *node1_ptr = rcl_get_zero_initialized_node(); const char * node1_name = "node1"; rcl_node_options_t node1_options = rcl_node_get_default_options(); - ret = rcl_node_init(node1_ptr, node1_name, &node1_options); + ret = rcl_node_init(node1_ptr, node1_name, "", &node1_options); ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe(); auto node2_ptr = new rcl_node_t; *node2_ptr = rcl_get_zero_initialized_node(); const char * node2_name = "node2"; rcl_node_options_t node2_options = rcl_node_get_default_options(); - ret = rcl_node_init(node2_ptr, node2_name, &node2_options); + ret = rcl_node_init(node2_ptr, node2_name, "", &node2_options); ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe(); std::this_thread::sleep_for(1s); diff --git a/rcl/test/rcl/test_graph.cpp b/rcl/test/rcl/test_graph.cpp index fe14817..97777da 100644 --- a/rcl/test/rcl/test_graph.cpp +++ b/rcl/test/rcl/test_graph.cpp @@ -63,7 +63,7 @@ public: *this->old_node_ptr = rcl_get_zero_initialized_node(); const char * old_name = "old_node_name"; rcl_node_options_t node_options = rcl_node_get_default_options(); - ret = rcl_node_init(this->old_node_ptr, old_name, &node_options); + ret = rcl_node_init(this->old_node_ptr, old_name, "", &node_options); ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe(); ret = rcl_shutdown(); // after this, the old_node_ptr should be invalid EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe(); @@ -73,7 +73,7 @@ public: this->node_ptr = new rcl_node_t; *this->node_ptr = rcl_get_zero_initialized_node(); const char * name = "node_name"; - ret = rcl_node_init(this->node_ptr, name, &node_options); + ret = rcl_node_init(this->node_ptr, name, "", &node_options); ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe(); this->wait_set_ptr = new rcl_wait_set_t; diff --git a/rcl/test/rcl/test_node.cpp b/rcl/test/rcl/test_node.cpp index de7909d..27ed3cd 100644 --- a/rcl/test/rcl/test_node.cpp +++ b/rcl/test/rcl/test_node.cpp @@ -73,15 +73,16 @@ TEST_F(CLASSNAME(TestNodeFixture, RMW_IMPLEMENTATION), test_rcl_node_accessors) // Create an invalid node (rcl_shutdown). rcl_node_t invalid_node = rcl_get_zero_initialized_node(); const char * name = "node_name"; + const char * namespace_ = "/ns"; rcl_node_options_t default_options = rcl_node_get_default_options(); default_options.domain_id = 42; // Set the domain id to something explicit. - ret = rcl_node_init(&invalid_node, name, &default_options); + ret = rcl_node_init(&invalid_node, name, namespace_, &default_options); if (is_windows && is_opensplice) { // On Windows with OpenSplice, setting the domain id is not expected to work. ASSERT_NE(RCL_RET_OK, ret); // So retry with the default domain id setting (uses the environment as is). default_options.domain_id = rcl_node_get_default_options().domain_id; - ret = rcl_node_init(&invalid_node, name, &default_options); + ret = rcl_node_init(&invalid_node, name, namespace_, &default_options); ASSERT_EQ(RCL_RET_OK, ret); } else { // This is the normal check (not windows and windows if not opensplice) @@ -105,7 +106,7 @@ TEST_F(CLASSNAME(TestNodeFixture, RMW_IMPLEMENTATION), test_rcl_node_accessors) rcl_node_t zero_node = rcl_get_zero_initialized_node(); // Create a normal node. rcl_node_t node = rcl_get_zero_initialized_node(); - ret = rcl_node_init(&node, name, &default_options); + ret = rcl_node_init(&node, name, namespace_, &default_options); ASSERT_EQ(RCL_RET_OK, ret); auto rcl_node_exit = make_scope_exit([&node]() { stop_memory_checking(); @@ -150,6 +151,30 @@ TEST_F(CLASSNAME(TestNodeFixture, RMW_IMPLEMENTATION), test_rcl_node_accessors) if (actual_node_name) { EXPECT_EQ(std::string(name), std::string(actual_node_name)); } + // Test rcl_node_get_namespace(). + const char * actual_node_namespace; + actual_node_namespace = rcl_node_get_namespace(nullptr); + EXPECT_EQ(nullptr, actual_node_namespace); + rcl_reset_error(); + actual_node_namespace = rcl_node_get_namespace(&zero_node); + EXPECT_EQ(nullptr, actual_node_namespace); + rcl_reset_error(); + actual_node_namespace = rcl_node_get_namespace(&invalid_node); + EXPECT_EQ(nullptr, actual_node_namespace); + rcl_reset_error(); + start_memory_checking(); + assert_no_malloc_begin(); + assert_no_realloc_begin(); + assert_no_free_begin(); + actual_node_namespace = rcl_node_get_namespace(&node); + assert_no_malloc_end(); + assert_no_realloc_end(); + assert_no_free_end(); + stop_memory_checking(); + EXPECT_TRUE(actual_node_namespace ? true : false); + if (actual_node_namespace) { + EXPECT_EQ(std::string(namespace_), std::string(actual_node_namespace)); + } // Test rcl_node_get_options(). const rcl_node_options_t * actual_options; actual_options = rcl_node_get_options(nullptr); @@ -179,12 +204,15 @@ TEST_F(CLASSNAME(TestNodeFixture, RMW_IMPLEMENTATION), test_rcl_node_accessors) size_t actual_domain_id; ret = rcl_node_get_domain_id(nullptr, &actual_domain_id); EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret); + ASSERT_TRUE(rcl_error_is_set()); rcl_reset_error(); ret = rcl_node_get_domain_id(&zero_node, &actual_domain_id); EXPECT_EQ(RCL_RET_NODE_INVALID, ret); + ASSERT_TRUE(rcl_error_is_set()); rcl_reset_error(); ret = rcl_node_get_domain_id(&invalid_node, &actual_domain_id); EXPECT_EQ(RCL_RET_NODE_INVALID, ret); + ASSERT_TRUE(rcl_error_is_set()); rcl_reset_error(); start_memory_checking(); assert_no_malloc_begin(); @@ -278,10 +306,12 @@ TEST_F(CLASSNAME(TestNodeFixture, RMW_IMPLEMENTATION), test_rcl_node_life_cycle) rcl_ret_t ret; rcl_node_t node = rcl_get_zero_initialized_node(); const char * name = "node_name"; + const char * namespace_ = "/ns"; rcl_node_options_t default_options = rcl_node_get_default_options(); // Trying to init before rcl_init() should fail. - ret = rcl_node_init(&node, name, &default_options); + ret = rcl_node_init(&node, name, "", &default_options); ASSERT_EQ(RCL_RET_NOT_INIT, ret) << "Expected RCL_RET_NOT_INIT"; + ASSERT_TRUE(rcl_error_is_set()); rcl_reset_error(); // Initialize rcl with rcl_init(). ret = rcl_init(0, nullptr, rcl_get_default_allocator()); @@ -291,48 +321,60 @@ TEST_F(CLASSNAME(TestNodeFixture, RMW_IMPLEMENTATION), test_rcl_node_life_cycle) ASSERT_EQ(RCL_RET_OK, ret); }); // Try invalid arguments. - ret = rcl_node_init(nullptr, name, &default_options); + ret = rcl_node_init(nullptr, name, namespace_, &default_options); EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret); + ASSERT_TRUE(rcl_error_is_set()); rcl_reset_error(); - ret = rcl_node_init(&node, nullptr, &default_options); + ret = rcl_node_init(&node, nullptr, namespace_, &default_options); EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret); + ASSERT_TRUE(rcl_error_is_set()); rcl_reset_error(); - ret = rcl_node_init(&node, name, nullptr); + ret = rcl_node_init(&node, name, nullptr, &default_options); EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret); + ASSERT_TRUE(rcl_error_is_set()); + rcl_reset_error(); + ret = rcl_node_init(&node, name, namespace_, nullptr); + EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret); + ASSERT_TRUE(rcl_error_is_set()); rcl_reset_error(); // Try with invalid allocator. rcl_node_options_t options_with_invalid_allocator = rcl_node_get_default_options(); options_with_invalid_allocator.allocator.allocate = nullptr; options_with_invalid_allocator.allocator.deallocate = nullptr; options_with_invalid_allocator.allocator.reallocate = nullptr; - ret = rcl_node_init(&node, name, &options_with_invalid_allocator); + ret = rcl_node_init(&node, name, namespace_, &options_with_invalid_allocator); EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret) << "Expected RCL_RET_INVALID_ARGUMENT"; + ASSERT_TRUE(rcl_error_is_set()); rcl_reset_error(); // Try with failing allocator. rcl_node_options_t options_with_failing_allocator = rcl_node_get_default_options(); options_with_failing_allocator.allocator.allocate = failing_malloc; options_with_failing_allocator.allocator.deallocate = failing_free; options_with_failing_allocator.allocator.reallocate = failing_realloc; - ret = rcl_node_init(&node, name, &options_with_failing_allocator); + ret = rcl_node_init(&node, name, namespace_, &options_with_failing_allocator); EXPECT_EQ(RCL_RET_BAD_ALLOC, ret) << "Expected RCL_RET_BAD_ALLOC"; + ASSERT_TRUE(rcl_error_is_set()); rcl_reset_error(); // Try fini with invalid arguments. ret = rcl_node_fini(nullptr); EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret) << "Expected RCL_RET_INVALID_ARGUMENT"; + ASSERT_TRUE(rcl_error_is_set()); rcl_reset_error(); // Try fini with an uninitialized node. ret = rcl_node_fini(&node); EXPECT_EQ(RCL_RET_OK, ret); // Try a normal init and fini. - ret = rcl_node_init(&node, name, &default_options); + ret = rcl_node_init(&node, name, namespace_, &default_options); EXPECT_EQ(RCL_RET_OK, ret); ret = rcl_node_fini(&node); EXPECT_EQ(RCL_RET_OK, ret); // Try repeated init and fini calls. - ret = rcl_node_init(&node, name, &default_options); + ret = rcl_node_init(&node, name, namespace_, &default_options); EXPECT_EQ(RCL_RET_OK, ret); - ret = rcl_node_init(&node, name, &default_options); + ret = rcl_node_init(&node, name, namespace_, &default_options); EXPECT_EQ(RCL_RET_ALREADY_INIT, ret) << "Expected RCL_RET_ALREADY_INIT"; + ASSERT_TRUE(rcl_error_is_set()); + rcl_reset_error(); ret = rcl_node_fini(&node); EXPECT_EQ(RCL_RET_OK, ret); ret = rcl_node_fini(&node); @@ -340,7 +382,7 @@ TEST_F(CLASSNAME(TestNodeFixture, RMW_IMPLEMENTATION), test_rcl_node_life_cycle) // Try with a specific domain id. rcl_node_options_t options_with_custom_domain_id = rcl_node_get_default_options(); options_with_custom_domain_id.domain_id = 42; - ret = rcl_node_init(&node, name, &options_with_custom_domain_id); + ret = rcl_node_init(&node, name, namespace_, &options_with_custom_domain_id); if (is_windows && is_opensplice) { // A custom domain id is not expected to work on Windows with Opensplice. EXPECT_NE(RCL_RET_OK, ret); @@ -351,3 +393,161 @@ TEST_F(CLASSNAME(TestNodeFixture, RMW_IMPLEMENTATION), test_rcl_node_life_cycle) EXPECT_EQ(RCL_RET_OK, ret); } } + +/* Tests the node name restrictions enforcement. + */ +TEST_F(CLASSNAME(TestNodeFixture, RMW_IMPLEMENTATION), test_rcl_node_name_restrictions) { + stop_memory_checking(); + rcl_ret_t ret; + + // Initialize rcl with rcl_init(). + ret = rcl_init(0, nullptr, rcl_get_default_allocator()); + ASSERT_EQ(RCL_RET_OK, ret); + auto rcl_shutdown_exit = make_scope_exit([]() { + rcl_ret_t ret = rcl_shutdown(); + ASSERT_EQ(RCL_RET_OK, ret); + }); + + const char * namespace_ = "/ns"; + rcl_node_options_t default_options = rcl_node_get_default_options(); + + // First do a normal node name. + { + rcl_node_t node = rcl_get_zero_initialized_node(); + ret = rcl_node_init(&node, "my_node_42", namespace_, &default_options); + ASSERT_EQ(RCL_RET_OK, ret); + rcl_ret_t ret = rcl_node_fini(&node); + EXPECT_EQ(RCL_RET_OK, ret); + } + + // Node name with invalid characters. + { + rcl_node_t node = rcl_get_zero_initialized_node(); + ret = rcl_node_init(&node, "my_node_42$", namespace_, &default_options); + ASSERT_EQ(RCL_RET_NODE_INVALID_NAME, ret); + ASSERT_TRUE(rcl_error_is_set()); + rcl_reset_error(); + rcl_ret_t ret = rcl_node_fini(&node); + EXPECT_EQ(RCL_RET_OK, ret); + } + + // Node name with /, which is valid in a topic, but not a node name. + { + rcl_node_t node = rcl_get_zero_initialized_node(); + ret = rcl_node_init(&node, "my/node_42", namespace_, &default_options); + ASSERT_EQ(RCL_RET_NODE_INVALID_NAME, ret); + ASSERT_TRUE(rcl_error_is_set()); + rcl_reset_error(); + rcl_ret_t ret = rcl_node_fini(&node); + EXPECT_EQ(RCL_RET_OK, ret); + } + + // Node name with {}, which is valid in a topic, but not a node name. + { + rcl_node_t node = rcl_get_zero_initialized_node(); + ret = rcl_node_init(&node, "my_{node}_42", namespace_, &default_options); + ASSERT_EQ(RCL_RET_NODE_INVALID_NAME, ret); + ASSERT_TRUE(rcl_error_is_set()); + rcl_reset_error(); + rcl_ret_t ret = rcl_node_fini(&node); + EXPECT_EQ(RCL_RET_OK, ret); + } +} + +/* Tests the node namespace restrictions enforcement. + */ +TEST_F(CLASSNAME(TestNodeFixture, RMW_IMPLEMENTATION), test_rcl_node_namespace_restrictions) { + stop_memory_checking(); + rcl_ret_t ret; + + // Initialize rcl with rcl_init(). + ret = rcl_init(0, nullptr, rcl_get_default_allocator()); + ASSERT_EQ(RCL_RET_OK, ret); + auto rcl_shutdown_exit = make_scope_exit([]() { + rcl_ret_t ret = rcl_shutdown(); + ASSERT_EQ(RCL_RET_OK, ret); + }); + + const char * name = "node"; + rcl_node_options_t default_options = rcl_node_get_default_options(); + + // First do a normal node namespace. + { + rcl_node_t node = rcl_get_zero_initialized_node(); + ret = rcl_node_init(&node, name, "/ns", &default_options); + ASSERT_EQ(RCL_RET_OK, ret); + rcl_ret_t ret = rcl_node_fini(&node); + EXPECT_EQ(RCL_RET_OK, ret); + } + + // Node namespace which is an empty string, which is also valid. + { + rcl_node_t node = rcl_get_zero_initialized_node(); + ret = rcl_node_init(&node, name, "", &default_options); + ASSERT_EQ(RCL_RET_OK, ret); + ASSERT_STREQ("/", rcl_node_get_namespace(&node)); + rcl_ret_t ret = rcl_node_fini(&node); + EXPECT_EQ(RCL_RET_OK, ret); + } + + // Node namespace which is just a forward slash, which is valid. + { + rcl_node_t node = rcl_get_zero_initialized_node(); + ret = rcl_node_init(&node, name, "/", &default_options); + ASSERT_EQ(RCL_RET_OK, ret); + rcl_ret_t ret = rcl_node_fini(&node); + EXPECT_EQ(RCL_RET_OK, ret); + } + + // Node namespaces with invalid characters. + { + rcl_node_t node = rcl_get_zero_initialized_node(); + ret = rcl_node_init(&node, name, "/ns/{name}", &default_options); + ASSERT_EQ(RCL_RET_NODE_INVALID_NAMESPACE, ret); + ASSERT_TRUE(rcl_error_is_set()); + rcl_reset_error(); + rcl_ret_t ret = rcl_node_fini(&node); + EXPECT_EQ(RCL_RET_OK, ret); + } + { + rcl_node_t node = rcl_get_zero_initialized_node(); + ret = rcl_node_init(&node, name, "/~/", &default_options); + ASSERT_EQ(RCL_RET_NODE_INVALID_NAMESPACE, ret); + ASSERT_TRUE(rcl_error_is_set()); + rcl_reset_error(); + rcl_ret_t ret = rcl_node_fini(&node); + EXPECT_EQ(RCL_RET_OK, ret); + } + + // Node namespace with a trailing / which is not allowed. + { + rcl_node_t node = rcl_get_zero_initialized_node(); + ret = rcl_node_init(&node, name, "/ns/foo/", &default_options); + ASSERT_EQ(RCL_RET_NODE_INVALID_NAMESPACE, ret); + ASSERT_TRUE(rcl_error_is_set()); + rcl_reset_error(); + rcl_ret_t ret = rcl_node_fini(&node); + EXPECT_EQ(RCL_RET_OK, ret); + } + + // Node namespace which is not absolute, it should get / added automatically. + { + rcl_node_t node = rcl_get_zero_initialized_node(); + ret = rcl_node_init(&node, name, "ns", &default_options); + ASSERT_EQ(RCL_RET_OK, ret); + ASSERT_STREQ("/ns", rcl_node_get_namespace(&node)); + rcl_ret_t ret = rcl_node_fini(&node); + EXPECT_EQ(RCL_RET_OK, ret); + } + + // Other reasons for being invalid, which are related to being part of a topic. + { + rcl_node_t node = rcl_get_zero_initialized_node(); + ret = rcl_node_init(&node, name, "/starts/with/42number", &default_options); + ASSERT_EQ(RCL_RET_NODE_INVALID_NAMESPACE, ret); + ASSERT_TRUE(rcl_error_is_set()); + rcl_reset_error(); + rcl_ret_t ret = rcl_node_fini(&node); + EXPECT_EQ(RCL_RET_OK, ret); + } +} diff --git a/rcl/test/rcl/test_publisher.cpp b/rcl/test/rcl/test_publisher.cpp index d296330..788c1e1 100644 --- a/rcl/test/rcl/test_publisher.cpp +++ b/rcl/test/rcl/test_publisher.cpp @@ -46,7 +46,7 @@ public: *this->node_ptr = rcl_get_zero_initialized_node(); const char * name = "node_name"; rcl_node_options_t node_options = rcl_node_get_default_options(); - ret = rcl_node_init(this->node_ptr, name, &node_options); + ret = rcl_node_init(this->node_ptr, name, "", &node_options); ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe(); set_on_unexpected_malloc_callback([]() {ASSERT_FALSE(true) << "UNEXPECTED MALLOC";}); set_on_unexpected_realloc_callback([]() {ASSERT_FALSE(true) << "UNEXPECTED REALLOC";}); diff --git a/rcl/test/rcl/test_service.cpp b/rcl/test/rcl/test_service.cpp index 4e63019..96cb6ed 100644 --- a/rcl/test/rcl/test_service.cpp +++ b/rcl/test/rcl/test_service.cpp @@ -49,7 +49,7 @@ public: *this->node_ptr = rcl_get_zero_initialized_node(); const char * name = "node_name"; rcl_node_options_t node_options = rcl_node_get_default_options(); - ret = rcl_node_init(this->node_ptr, name, &node_options); + ret = rcl_node_init(this->node_ptr, name, "", &node_options); ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe(); set_on_unexpected_malloc_callback([]() {ASSERT_FALSE(true) << "UNEXPECTED MALLOC";}); set_on_unexpected_realloc_callback([]() {ASSERT_FALSE(true) << "UNEXPECTED REALLOC";}); diff --git a/rcl/test/rcl/test_subscription.cpp b/rcl/test/rcl/test_subscription.cpp index 9d783a8..f3693c6 100644 --- a/rcl/test/rcl/test_subscription.cpp +++ b/rcl/test/rcl/test_subscription.cpp @@ -50,7 +50,7 @@ public: *this->node_ptr = rcl_get_zero_initialized_node(); const char * name = "node_name"; rcl_node_options_t node_options = rcl_node_get_default_options(); - ret = rcl_node_init(this->node_ptr, name, &node_options); + ret = rcl_node_init(this->node_ptr, name, "", &node_options); ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe(); set_on_unexpected_malloc_callback([]() {ASSERT_FALSE(true) << "UNEXPECTED MALLOC";}); set_on_unexpected_realloc_callback([]() {ASSERT_FALSE(true) << "UNEXPECTED REALLOC";}); diff --git a/rcl_lifecycle/test/test_default_state_machine.cpp b/rcl_lifecycle/test/test_default_state_machine.cpp index 1d2774c..88c724a 100644 --- a/rcl_lifecycle/test/test_default_state_machine.cpp +++ b/rcl_lifecycle/test/test_default_state_machine.cpp @@ -42,7 +42,7 @@ protected: *this->node_ptr = rcl_get_zero_initialized_node(); const char * name = "node_name"; rcl_node_options_t node_options = rcl_node_get_default_options(); - ret = rcl_node_init(this->node_ptr, name, &node_options); + ret = rcl_node_init(this->node_ptr, name, "", &node_options); ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe(); } diff --git a/rcl_lifecycle/test/test_multiple_instances.cpp b/rcl_lifecycle/test/test_multiple_instances.cpp index 4a34020..1b9141b 100644 --- a/rcl_lifecycle/test/test_multiple_instances.cpp +++ b/rcl_lifecycle/test/test_multiple_instances.cpp @@ -42,7 +42,7 @@ protected: *this->node_ptr = rcl_get_zero_initialized_node(); const char * name = "node_name"; rcl_node_options_t node_options = rcl_node_get_default_options(); - ret = rcl_node_init(this->node_ptr, name, &node_options); + ret = rcl_node_init(this->node_ptr, name, "", &node_options); ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe(); }