diff --git a/rcl/include/rcl/graph.h b/rcl/include/rcl/graph.h
index 59663d321..351e6f65c 100644
--- a/rcl/include/rcl/graph.h
+++ b/rcl/include/rcl/graph.h
@@ -775,6 +775,80 @@ rcl_wait_for_subscribers(
rcutils_duration_value_t timeout,
bool * success);
+/// Wait for there to be a specified number of clients on a given service.
+/**
+ * \see rcl_wait_for_publishers
+ *
+ *
+ * Attribute | Adherence
+ * ------------------ | -------------
+ * Allocates Memory | Yes
+ * Thread-Safe | No
+ * Uses Atomics | No
+ * Lock-Free | Maybe [1]
+ * [1] implementation may need to protect the data structure with a lock
+ *
+ * \param[in] node the handle to the node being used to query the ROS graph
+ * \param[in] allocator to allocate space for the rcl_wait_set_t used to wait for graph events
+ * \param[in] service_name the name of the topic in question
+ * \param[in] count number of clients to wait for
+ * \param[in] timeout maximum duration to wait for clients
+ * \param[out] success `true` if the number of clients is equal to or greater than count, or
+ * `false` if a timeout occurred waiting for clients.
+ * \return #RCL_RET_OK if there was no errors, or
+ * \return #RCL_RET_NODE_INVALID if the node is invalid, or
+ * \return #RCL_RET_INVALID_ARGUMENT if any arguments are invalid, or
+ * \return #RCL_RET_TIMEOUT if a timeout occurs before the number of clients is detected, or
+ * \return #RCL_RET_ERROR if an unspecified error occurred.
+ */
+RCL_PUBLIC
+RCL_WARN_UNUSED
+rcl_ret_t
+rcl_wait_for_clients(
+ const rcl_node_t * node,
+ rcl_allocator_t * allocator,
+ const char * service_name,
+ const size_t count,
+ rcutils_duration_value_t timeout,
+ bool * success);
+
+/// Wait for there to be a specified number of servers on a given service.
+/**
+ * \see rcl_wait_for_publishers
+ *
+ *
+ * Attribute | Adherence
+ * ------------------ | -------------
+ * Allocates Memory | Yes
+ * Thread-Safe | No
+ * Uses Atomics | No
+ * Lock-Free | Maybe [1]
+ * [1] implementation may need to protect the data structure with a lock
+ *
+ * \param[in] node the handle to the node being used to query the ROS graph
+ * \param[in] allocator to allocate space for the rcl_wait_set_t used to wait for graph events
+ * \param[in] service_name the name of the topic in question
+ * \param[in] count number of servers to wait for
+ * \param[in] timeout maximum duration to wait for servers
+ * \param[out] success `true` if the number of servers is equal to or greater than count, or
+ * `false` if a timeout occurred waiting for servers.
+ * \return #RCL_RET_OK if there was no errors, or
+ * \return #RCL_RET_NODE_INVALID if the node is invalid, or
+ * \return #RCL_RET_INVALID_ARGUMENT if any arguments are invalid, or
+ * \return #RCL_RET_TIMEOUT if a timeout occurs before the number of servers is detected, or
+ * \return #RCL_RET_ERROR if an unspecified error occurred.
+ */
+RCL_PUBLIC
+RCL_WARN_UNUSED
+rcl_ret_t
+rcl_wait_for_services(
+ const rcl_node_t * node,
+ rcl_allocator_t * allocator,
+ const char * service_name,
+ const size_t count,
+ rcutils_duration_value_t timeout,
+ bool * success);
+
/// Return a list of all publishers to a topic.
/**
* The `node` parameter must point to a valid node.
@@ -897,6 +971,128 @@ rcl_get_subscriptions_info_by_topic(
bool no_mangle,
rcl_topic_endpoint_info_array_t * subscriptions_info);
+/// Return a list of all clients to a service.
+/**
+ * The `node` parameter must point to a valid node.
+ *
+ * The `service_name` parameter must not be `NULL`.
+ *
+ * When the `no_mangle` parameter is `true`, the provided `service_name` should be a valid name
+ * for the middleware (useful when combining ROS with native middleware (e.g. DDS) apps).
+ * When the `no_mangle` parameter is `false`, the provided `service_name` should follow
+ * ROS topic name conventions.
+ * In either case, the service name should always be fully qualified.
+ *
+ * Each element in the `clients_info` array will contain the node name, node namespace,
+ * service type, gid and the qos profile of the client.
+ * It is the responsibility of the caller to ensure that `clients_info` parameter points
+ * to a valid struct of type rcl_topic_endpoint_info_array_t.
+ * The `count` field inside the struct must be set to 0 and the `info_array` field inside
+ * the struct must be set to null.
+ * \see rmw_get_zero_initialized_topic_endpoint_info_array
+ *
+ * The `allocator` will be used to allocate memory to the `info_array` member
+ * inside of `clients_info`.
+ * Moreover, every const char * member inside of
+ * rmw_topic_endpoint_info_t will be assigned a copied value on allocated memory.
+ * \see rmw_topic_endpoint_info_set_node_name and the likes.
+ * However, it is the responsibility of the caller to
+ * reclaim any allocated resources to `clients_info` to avoid leaking memory.
+ * \see rmw_topic_endpoint_info_array_fini
+ *
+ *
+ * Attribute | Adherence
+ * ------------------ | -------------
+ * Allocates Memory | Yes
+ * Thread-Safe | No
+ * Uses Atomics | No
+ * Lock-Free | Maybe [1]
+ * [1] implementation may need to protect the data structure with a lock
+ *
+ * \param[in] node the handle to the node being used to query the ROS graph
+ * \param[in] allocator allocator to be used when allocating space for
+ * the array inside clients_info
+ * \param[in] service_name the name of the service in question
+ * \param[in] no_mangle if `true`, `service_name` needs to be a valid middleware topic name,
+ * otherwise it should be a valid ROS topic name
+ * \param[out] clients_info a struct representing a list of client information
+ * \return #RCL_RET_OK if the query was successful, or
+ * \return #RCL_RET_NODE_INVALID if the node is invalid, or
+ * \return #RCL_RET_INVALID_ARGUMENT if any arguments are invalid, or
+ * \return #RCL_RET_BAD_ALLOC if memory allocation fails, or
+ * \return #RCL_RET_ERROR if an unspecified error occurs.
+ */
+RCL_PUBLIC
+RCL_WARN_UNUSED
+rcl_ret_t
+rcl_get_clients_info_by_service(
+ const rcl_node_t * node,
+ rcutils_allocator_t * allocator,
+ const char * service_name,
+ bool no_mangle,
+ rcl_topic_endpoint_info_array_t * clients_info);
+
+/// Return a list of all servers to a service.
+/**
+ * The `node` parameter must point to a valid node.
+ *
+ * The `service_name` parameter must not be `NULL`.
+ *
+ * When the `no_mangle` parameter is `true`, the provided `service_name` should be a valid name
+ * for the middleware (useful when combining ROS with native middleware (e.g. DDS) apps).
+ * When the `no_mangle` parameter is `false`, the provided `service_name` should follow
+ * ROS topic name conventions.
+ * In either case, the service name should always be fully qualified.
+ *
+ * Each element in the `servers_info` array will contain the node name, node namespace,
+ * service type, gid and the qos profile of the server.
+ * It is the responsibility of the caller to ensure that `servers_info` parameter points
+ * to a valid struct of type rcl_topic_endpoint_info_array_t.
+ * The `count` field inside the struct must be set to 0 and the `info_array` field inside
+ * the struct must be set to null.
+ * \see rmw_get_zero_initialized_topic_endpoint_info_array
+ *
+ * The `allocator` will be used to allocate memory to the `info_array` member
+ * inside of `servers_info`.
+ * Moreover, every const char * member inside of
+ * rmw_topic_endpoint_info_t will be assigned a copied value on allocated memory.
+ * \see rmw_topic_endpoint_info_set_node_name and the likes.
+ * However, it is the responsibility of the caller to
+ * reclaim any allocated resources to `servers_info` to avoid leaking memory.
+ * \see rmw_topic_endpoint_info_array_fini
+ *
+ *
+ * Attribute | Adherence
+ * ------------------ | -------------
+ * Allocates Memory | Yes
+ * Thread-Safe | No
+ * Uses Atomics | No
+ * Lock-Free | Maybe [1]
+ * [1] implementation may need to protect the data structure with a lock
+ *
+ * \param[in] node the handle to the node being used to query the ROS graph
+ * \param[in] allocator allocator to be used when allocating space for
+ * the array inside clients_info
+ * \param[in] service_name the name of the service in question
+ * \param[in] no_mangle if `true`, `service_name` needs to be a valid middleware topic name,
+ * otherwise it should be a valid ROS topic name
+ * \param[out] servers_info a struct representing a list of server information
+ * \return #RCL_RET_OK if the query was successful, or
+ * \return #RCL_RET_NODE_INVALID if the node is invalid, or
+ * \return #RCL_RET_INVALID_ARGUMENT if any arguments are invalid, or
+ * \return #RCL_RET_BAD_ALLOC if memory allocation fails, or
+ * \return #RCL_RET_ERROR if an unspecified error occurs.
+ */
+RCL_PUBLIC
+RCL_WARN_UNUSED
+rcl_ret_t
+rcl_get_servers_info_by_service(
+ const rcl_node_t * node,
+ rcutils_allocator_t * allocator,
+ const char * service_name,
+ bool no_mangle,
+ rcl_topic_endpoint_info_array_t * servers_info);
+
/// Check if a service server is available for the given service client.
/**
* This function will return true for `is_available` if there is a service server
diff --git a/rcl/include/rcl/init.h b/rcl/include/rcl/init.h
index a85aae609..c6ff929eb 100644
--- a/rcl/include/rcl/init.h
+++ b/rcl/include/rcl/init.h
@@ -50,7 +50,7 @@ extern "C"
*
* The `options` argument must be non-`NULL` and must have been initialized
* with rcl_init_options_init().
- * It is unmodified by this function, and the ownership is not transfered to
+ * It is unmodified by this function, and the ownership is not transferred to
* the context, but instead a copy is made into the context for later reference.
* Therefore, the given options need to be cleaned up with
* rcl_init_options_fini() after this function returns.
diff --git a/rcl/src/rcl/graph.c b/rcl/src/rcl/graph.c
index 323f0199b..c596a48c5 100644
--- a/rcl/src/rcl/graph.c
+++ b/rcl/src/rcl/graph.c
@@ -667,6 +667,44 @@ rcl_wait_for_subscribers(
rcl_count_subscribers);
}
+rcl_ret_t
+rcl_wait_for_clients(
+ const rcl_node_t * node,
+ rcl_allocator_t * allocator,
+ const char * service_name,
+ const size_t expected_count,
+ rcutils_duration_value_t timeout,
+ bool * success)
+{
+ return _rcl_wait_for_entities(
+ node,
+ allocator,
+ service_name,
+ expected_count,
+ timeout,
+ success,
+ rcl_count_clients);
+}
+
+rcl_ret_t
+rcl_wait_for_services(
+ const rcl_node_t * node,
+ rcl_allocator_t * allocator,
+ const char * service_name,
+ const size_t expected_count,
+ rcutils_duration_value_t timeout,
+ bool * success)
+{
+ return _rcl_wait_for_entities(
+ node,
+ allocator,
+ service_name,
+ expected_count,
+ timeout,
+ success,
+ rcl_count_services);
+}
+
typedef rmw_ret_t (* get_topic_endpoint_info_func_t)(
const rmw_node_t * node,
rcutils_allocator_t * allocator,
@@ -751,6 +789,40 @@ rcl_get_subscriptions_info_by_topic(
rmw_get_subscriptions_info_by_topic);
}
+rcl_ret_t
+rcl_get_clients_info_by_service(
+ const rcl_node_t * node,
+ rcutils_allocator_t * allocator,
+ const char * service_name,
+ bool no_mangle,
+ rmw_topic_endpoint_info_array_t * clients_info)
+{
+ return __rcl_get_info_by_topic(
+ node,
+ allocator,
+ service_name,
+ no_mangle,
+ clients_info,
+ rmw_get_clients_info_by_service);
+}
+
+rcl_ret_t
+rcl_get_servers_info_by_service(
+ const rcl_node_t * node,
+ rcutils_allocator_t * allocator,
+ const char * service_name,
+ bool no_mangle,
+ rmw_topic_endpoint_info_array_t * servers_info)
+{
+ return __rcl_get_info_by_topic(
+ node,
+ allocator,
+ service_name,
+ no_mangle,
+ servers_info,
+ rmw_get_servers_info_by_service);
+}
+
rcl_ret_t
rcl_service_server_is_available(
const rcl_node_t * node,
diff --git a/rcl/test/rcl/test_graph.cpp b/rcl/test/rcl/test_graph.cpp
index 99c21c760..162b12435 100644
--- a/rcl/test/rcl/test_graph.cpp
+++ b/rcl/test/rcl/test_graph.cpp
@@ -834,6 +834,90 @@ TEST_F(TestGraphFixture, test_rcl_wait_for_subscribers) {
rcl_reset_error();
}
+/* Test the rcl_wait_for_clients function.
+ */
+TEST_F(TestGraphFixture, test_rcl_wait_for_clients) {
+ rcl_ret_t ret;
+ rcl_node_t zero_node = rcl_get_zero_initialized_node();
+ rcl_allocator_t zero_allocator = static_cast(
+ rcutils_get_zero_initialized_allocator());
+ rcl_allocator_t allocator = rcl_get_default_allocator();
+ const char * service_name = "/topic_test_rcl_wait_for_clients";
+ bool success = false;
+
+ // Invalid node
+ ret = rcl_wait_for_clients(nullptr, &allocator, service_name, 1u, 100, &success);
+ EXPECT_EQ(RCL_RET_NODE_INVALID, ret);
+ rcl_reset_error();
+ ret = rcl_wait_for_clients(&zero_node, &allocator, service_name, 1u, 100, &success);
+ EXPECT_EQ(RCL_RET_NODE_INVALID, ret);
+ rcl_reset_error();
+ ret = rcl_wait_for_clients(this->old_node_ptr, &allocator, service_name, 1u, 100, &success);
+ EXPECT_EQ(RCL_RET_NODE_INVALID, ret) << rcl_get_error_string().str;
+ rcl_reset_error();
+ // Invalid allocator
+ ret = rcl_wait_for_clients(this->node_ptr, nullptr, service_name, 1u, 100, &success);
+ EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret) << rcl_get_error_string().str;
+ rcl_reset_error();
+ ret = rcl_wait_for_clients(this->node_ptr, &zero_allocator, service_name, 1u, 100, &success);
+ EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret) << rcl_get_error_string().str;
+ rcl_reset_error();
+ // Invalid topic name
+ ret = rcl_wait_for_clients(this->node_ptr, &allocator, nullptr, 1u, 100, &success);
+ EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret) << rcl_get_error_string().str;
+ rcl_reset_error();
+ // Invalid output arg
+ ret = rcl_wait_for_clients(this->node_ptr, &allocator, service_name, 1u, 100, nullptr);
+ EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret) << rcl_get_error_string().str;
+ rcl_reset_error();
+ // Valid call (expect timeout since there are no clients)
+ ret = rcl_wait_for_clients(this->node_ptr, &allocator, service_name, 1u, 100, &success);
+ EXPECT_EQ(RCL_RET_TIMEOUT, ret) << rcl_get_error_string().str;
+ rcl_reset_error();
+}
+
+/* Test the rcl_wait_for_clients function.
+ */
+TEST_F(TestGraphFixture, test_rcl_wait_for_services) {
+ rcl_ret_t ret;
+ rcl_node_t zero_node = rcl_get_zero_initialized_node();
+ rcl_allocator_t zero_allocator = static_cast(
+ rcutils_get_zero_initialized_allocator());
+ rcl_allocator_t allocator = rcl_get_default_allocator();
+ const char * service_name = "/topic_test_rcl_wait_for_services";
+ bool success = false;
+
+ // Invalid node
+ ret = rcl_wait_for_services(nullptr, &allocator, service_name, 1u, 100, &success);
+ EXPECT_EQ(RCL_RET_NODE_INVALID, ret);
+ rcl_reset_error();
+ ret = rcl_wait_for_services(&zero_node, &allocator, service_name, 1u, 100, &success);
+ EXPECT_EQ(RCL_RET_NODE_INVALID, ret);
+ rcl_reset_error();
+ ret = rcl_wait_for_services(this->old_node_ptr, &allocator, service_name, 1u, 100, &success);
+ EXPECT_EQ(RCL_RET_NODE_INVALID, ret) << rcl_get_error_string().str;
+ rcl_reset_error();
+ // Invalid allocator
+ ret = rcl_wait_for_services(this->node_ptr, nullptr, service_name, 1u, 100, &success);
+ EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret) << rcl_get_error_string().str;
+ rcl_reset_error();
+ ret = rcl_wait_for_services(this->node_ptr, &zero_allocator, service_name, 1u, 100, &success);
+ EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret) << rcl_get_error_string().str;
+ rcl_reset_error();
+ // Invalid topic name
+ ret = rcl_wait_for_services(this->node_ptr, &allocator, nullptr, 1u, 100, &success);
+ EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret) << rcl_get_error_string().str;
+ rcl_reset_error();
+ // Invalid output arg
+ ret = rcl_wait_for_services(this->node_ptr, &allocator, service_name, 1u, 100, nullptr);
+ EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret) << rcl_get_error_string().str;
+ rcl_reset_error();
+ // Valid call (expect timeout since there are no servers)
+ ret = rcl_wait_for_services(this->node_ptr, &allocator, service_name, 1u, 100, &success);
+ EXPECT_EQ(RCL_RET_TIMEOUT, ret) << rcl_get_error_string().str;
+ rcl_reset_error();
+}
+
void
check_entity_count(
const rcl_node_t * node_ptr,
diff --git a/rcl/test/rcl/test_info_by_topic.cpp b/rcl/test/rcl/test_info_by_topic.cpp
index de8849b31..934ada19d 100644
--- a/rcl/test/rcl/test_info_by_topic.cpp
+++ b/rcl/test/rcl/test_info_by_topic.cpp
@@ -30,6 +30,7 @@
#include "wait_for_entity_helpers.hpp"
#include "test_msgs/msg/strings.h"
+#include "test_msgs/srv/basic_types.h"
#include "rosidl_runtime_c/string_functions.h"
#include "osrf_testing_tools_cpp/scope_exit.hpp"
@@ -44,6 +45,7 @@ class TestInfoByTopicFixture : public ::testing::Test
const char * test_graph_node_name = "test_graph_node";
rmw_topic_endpoint_info_array_t topic_endpoint_info_array;
const char * const topic_name = "valid_topic_name";
+ const char * const service_name = "valid_service_name";
void SetUp()
{
@@ -149,6 +151,34 @@ TEST_F(TestInfoByTopicFixture, test_rcl_get_subscriptions_info_by_topic_null_nod
rcl_reset_error();
}
+/*
+ * This does not test content of the response.
+ * It only tests if the return code is the one expected.
+ */
+TEST_F(TestInfoByTopicFixture, test_rcl_get_clients_info_by_service_null_node)
+{
+ rcl_allocator_t allocator = rcl_get_default_allocator();
+ const auto ret = rcl_get_clients_info_by_service(
+ nullptr, &allocator, this->service_name, false,
+ &this->topic_endpoint_info_array);
+ EXPECT_EQ(RCL_RET_NODE_INVALID, ret);
+ rcl_reset_error();
+}
+
+/*
+ * This does not test content of the response.
+ * It only tests if the return code is the one expected.
+ */
+TEST_F(TestInfoByTopicFixture, test_rcl_get_servers_info_by_service_null_node)
+{
+ rcl_allocator_t allocator = rcl_get_default_allocator();
+ const auto ret = rcl_get_servers_info_by_service(
+ nullptr, &allocator, this->service_name, false,
+ &this->topic_endpoint_info_array);
+ EXPECT_EQ(RCL_RET_NODE_INVALID, ret);
+ rcl_reset_error();
+}
+
/*
* This does not test content of the response.
* It only tests if the return code is the one expected.
@@ -179,6 +209,36 @@ TEST_F(TestInfoByTopicFixture, test_rcl_get_subscriptions_info_by_topic_invalid_
rcl_reset_error();
}
+/*
+ * This does not test content of the response.
+ * It only tests if the return code is the one expected.
+ */
+TEST_F(TestInfoByTopicFixture, test_rcl_get_clients_info_by_service_invalid_node)
+{
+ // this->old_node is an invalid node.
+ rcl_allocator_t allocator = rcl_get_default_allocator();
+ const auto ret = rcl_get_clients_info_by_service(
+ &this->old_node, &allocator, this->service_name, false,
+ &this->topic_endpoint_info_array);
+ EXPECT_EQ(RCL_RET_NODE_INVALID, ret);
+ rcl_reset_error();
+}
+
+/*
+ * This does not test content of the response.
+ * It only tests if the return code is the one expected.
+ */
+TEST_F(TestInfoByTopicFixture, test_rcl_get_servers_info_by_service_invalid_node)
+{
+ // this->old_node is an invalid node.
+ rcl_allocator_t allocator = rcl_get_default_allocator();
+ const auto ret = rcl_get_servers_info_by_service(
+ &this->old_node, &allocator, this->service_name, false,
+ &this->topic_endpoint_info_array);
+ EXPECT_EQ(RCL_RET_NODE_INVALID, ret);
+ rcl_reset_error();
+}
+
/*
* This does not test content of the response.
* It only tests if the return code is the one expected.
@@ -205,6 +265,32 @@ TEST_F(TestInfoByTopicFixture, test_rcl_get_subscriptions_info_by_topic_null_all
rcl_reset_error();
}
+/*
+ * This does not test content of the response.
+ * It only tests if the return code is the one expected.
+ */
+TEST_F(TestInfoByTopicFixture, test_rcl_get_clients_info_by_service_null_allocator)
+{
+ const auto ret = rcl_get_clients_info_by_service(
+ &this->node, nullptr, this->service_name, false,
+ &this->topic_endpoint_info_array);
+ EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret);
+ rcl_reset_error();
+}
+
+/*
+ * This does not test content of the response.
+ * It only tests if the return code is the one expected.
+ */
+TEST_F(TestInfoByTopicFixture, test_rcl_get_servers_info_by_service_null_allocator)
+{
+ const auto ret = rcl_get_servers_info_by_service(
+ &this->node, nullptr, this->service_name, false,
+ &this->topic_endpoint_info_array);
+ EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret);
+ rcl_reset_error();
+}
+
/*
* This does not test content of the response.
* It only tests if the return code is the one expected.
@@ -231,6 +317,32 @@ TEST_F(TestInfoByTopicFixture, test_rcl_get_subscriptions_info_by_topic_null_top
rcl_reset_error();
}
+/*
+ * This does not test content of the response.
+ * It only tests if the return code is the one expected.
+ */
+TEST_F(TestInfoByTopicFixture, test_rcl_get_clients_info_by_service_null_service)
+{
+ rcl_allocator_t allocator = rcl_get_default_allocator();
+ const auto ret = rcl_get_clients_info_by_service(
+ &this->node, &allocator, nullptr, false, &this->topic_endpoint_info_array);
+ EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret);
+ rcl_reset_error();
+}
+
+/*
+ * This does not test content of the response.
+ * It only tests if the return code is the one expected.
+ */
+TEST_F(TestInfoByTopicFixture, test_rcl_get_servers_info_by_service_null_service)
+{
+ rcl_allocator_t allocator = rcl_get_default_allocator();
+ const auto ret = rcl_get_servers_info_by_service(
+ &this->node, &allocator, nullptr, false, &this->topic_endpoint_info_array);
+ EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret);
+ rcl_reset_error();
+}
+
/*
* This does not test content of the response.
* It only tests if the return code is the one expected.
@@ -257,6 +369,32 @@ TEST_F(TestInfoByTopicFixture, test_rcl_get_subscriptions_info_by_topic_null_par
rcl_reset_error();
}
+/*
+ * This does not test content of the response.
+ * It only tests if the return code is the one expected.
+ */
+TEST_F(TestInfoByTopicFixture, test_rcl_get_clients_info_by_service_null_participants)
+{
+ rcl_allocator_t allocator = rcl_get_default_allocator();
+ const auto ret = rcl_get_clients_info_by_service(
+ &this->node, &allocator, this->service_name, false, nullptr);
+ EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret);
+ rcl_reset_error();
+}
+
+/*
+ * This does not test content of the response.
+ * It only tests if the return code is the one expected.
+ */
+TEST_F(TestInfoByTopicFixture, test_rcl_get_servers_info_by_service_null_participants)
+{
+ rcl_allocator_t allocator = rcl_get_default_allocator();
+ const auto ret = rcl_get_servers_info_by_service(
+ &this->node, &allocator, this->service_name, false, nullptr);
+ EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret);
+ rcl_reset_error();
+}
+
/*
* This does not test content of the response.
* It only tests if the return code is the one expected.
@@ -299,6 +437,48 @@ TEST_F(TestInfoByTopicFixture, test_rcl_get_subscriptions_info_by_topic_invalid_
rcl_reset_error();
}
+/*
+ * This does not test content of the response.
+ * It only tests if the return code is the one expected.
+ */
+TEST_F(TestInfoByTopicFixture, test_rcl_get_clients_info_by_service_invalid_participants)
+{
+ // topic_endpoint_info_array is invalid because it is expected to be zero initialized
+ // and the info_array variable inside it is expected to be null.
+ this->topic_endpoint_info_array.info_array = new rmw_topic_endpoint_info_t();
+ OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT(
+ {
+ delete this->topic_endpoint_info_array.info_array;
+ });
+ rcl_allocator_t allocator = rcl_get_default_allocator();
+ const auto ret = rcl_get_clients_info_by_service(
+ &this->node, &allocator, this->service_name, false,
+ &this->topic_endpoint_info_array);
+ EXPECT_EQ(RCL_RET_ERROR, ret);
+ rcl_reset_error();
+}
+
+/*
+ * This does not test content of the response.
+ * It only tests if the return code is the one expected.
+ */
+TEST_F(TestInfoByTopicFixture, test_rcl_get_servers_info_by_service_invalid_participants)
+{
+ // topic_endpoint_info_array is invalid because it is expected to be zero initialized
+ // and the info_array variable inside it is expected to be null.
+ this->topic_endpoint_info_array.info_array = new rmw_topic_endpoint_info_t();
+ OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT(
+ {
+ delete this->topic_endpoint_info_array.info_array;
+ });
+ rcl_allocator_t allocator = rcl_get_default_allocator();
+ const auto ret = rcl_get_servers_info_by_service(
+ &this->node, &allocator, this->service_name, false,
+ &this->topic_endpoint_info_array);
+ EXPECT_EQ(RCL_RET_ERROR, ret);
+ rcl_reset_error();
+}
+
TEST_F(TestInfoByTopicFixture, test_rcl_get_publishers_subscription_info_by_topic)
{
rmw_qos_profile_t default_qos_profile = rmw_qos_profile_system_default;
@@ -388,3 +568,113 @@ TEST_F(TestInfoByTopicFixture, test_rcl_get_publishers_subscription_info_by_topi
ret = rcl_publisher_fini(&publisher, &this->node);
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
}
+
+TEST_F(TestInfoByTopicFixture, test_rcl_get_client_server_info_by_service)
+{
+ rmw_qos_profile_t default_qos_profile = rmw_qos_profile_system_default;
+ default_qos_profile.history = RMW_QOS_POLICY_HISTORY_KEEP_LAST;
+ default_qos_profile.depth = 0;
+ default_qos_profile.reliability = RMW_QOS_POLICY_RELIABILITY_BEST_EFFORT;
+ default_qos_profile.durability = RMW_QOS_POLICY_DURABILITY_VOLATILE;
+ default_qos_profile.lifespan = {10, 0};
+ default_qos_profile.deadline = {11, 0};
+ default_qos_profile.liveliness_lease_duration = {20, 0};
+ default_qos_profile.liveliness = RMW_QOS_POLICY_LIVELINESS_MANUAL_BY_TOPIC;
+
+ rcl_ret_t ret;
+ const rosidl_service_type_support_t * ts = ROSIDL_GET_SRV_TYPE_SUPPORT(
+ test_msgs, srv, BasicTypes);
+ rcl_allocator_t allocator = rcl_get_default_allocator();
+
+ rcl_client_t client = rcl_get_zero_initialized_client();
+ rcl_client_options_t client_options = rcl_client_get_default_options();
+ client_options.qos = default_qos_profile;
+ ret = rcl_client_init(
+ &client,
+ &this->node,
+ ts,
+ this->service_name,
+ &client_options
+ );
+ ASSERT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
+
+ rcl_service_t server = rcl_get_zero_initialized_service();
+ rcl_service_options_t server_options = rcl_service_get_default_options();
+ server_options.qos = default_qos_profile;
+ ret = rcl_service_init(
+ &server,
+ &this->node,
+ ts,
+ this->service_name,
+ &server_options
+ );
+ ASSERT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
+
+ const std::string fqdn = std::string("/") + this->service_name;
+ // Wait until GraphCache clients are updated
+ bool success = false;
+ ret = rcl_wait_for_clients(
+ &this->node, &allocator, fqdn.c_str(), 1u, RCUTILS_S_TO_NS(1), &success);
+ ASSERT_EQ(ret, RCL_RET_OK);
+ ASSERT_TRUE(success);
+ // Get clients info by service
+ rmw_topic_endpoint_info_array_t topic_endpoint_info_array_client =
+ rmw_get_zero_initialized_topic_endpoint_info_array();
+ ret = rcl_get_clients_info_by_service(
+ &this->node, &allocator, fqdn.c_str(), false,
+ &topic_endpoint_info_array_client
+ );
+ EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
+ EXPECT_GE(topic_endpoint_info_array_client.size, 1u) << "Expected at least one topic info";
+ rmw_topic_endpoint_info_t topic_endpoint_info_client;
+ for(size_t i = 0; i < topic_endpoint_info_array_client.size; i++) {
+ topic_endpoint_info_client = topic_endpoint_info_array_client.info_array[i];
+ EXPECT_STREQ(topic_endpoint_info_client.node_name, this->test_graph_node_name);
+ EXPECT_STREQ(topic_endpoint_info_client.node_namespace, "/");
+ if(topic_endpoint_info_client.endpoint_type == RMW_ENDPOINT_PUBLISHER) {
+ EXPECT_STREQ(topic_endpoint_info_client.topic_type, "test_msgs/srv/BasicTypes_Request");
+ assert_qos_equality(topic_endpoint_info_client.qos_profile, default_qos_profile, true);
+ } else if(topic_endpoint_info_client.endpoint_type == RMW_ENDPOINT_SUBSCRIPTION) {
+ EXPECT_STREQ(topic_endpoint_info_client.topic_type, "test_msgs/srv/BasicTypes_Response");
+ assert_qos_equality(topic_endpoint_info_client.qos_profile, default_qos_profile, false);
+ }
+ }
+ // Wait until GraphCache servers are updated
+ success = false;
+ ret = rcl_wait_for_services(
+ &this->node, &allocator, fqdn.c_str(), 1u, RCUTILS_S_TO_NS(1), &success);
+ ASSERT_EQ(ret, RCL_RET_OK);
+ ASSERT_TRUE(success);
+ // Get servers info by service
+ rmw_topic_endpoint_info_array_t topic_endpoint_info_array_server =
+ rmw_get_zero_initialized_topic_endpoint_info_array();
+ ret = rcl_get_servers_info_by_service(
+ &this->node, &allocator, fqdn.c_str(), false,
+ &topic_endpoint_info_array_server
+ );
+ EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
+ EXPECT_GE(topic_endpoint_info_array_server.size, 1u) << "Expected at least one topic info";
+ rmw_topic_endpoint_info_t topic_endpoint_info_server;
+ for(size_t i = 0; i < topic_endpoint_info_array_server.size; i++) {
+ topic_endpoint_info_server = topic_endpoint_info_array_server.info_array[i];
+ EXPECT_STREQ(topic_endpoint_info_server.node_name, this->test_graph_node_name);
+ EXPECT_STREQ(topic_endpoint_info_server.node_namespace, "/");
+ if(topic_endpoint_info_server.endpoint_type == RMW_ENDPOINT_PUBLISHER) {
+ EXPECT_STREQ(topic_endpoint_info_server.topic_type, "test_msgs/srv/BasicTypes_Response");
+ assert_qos_equality(topic_endpoint_info_server.qos_profile, default_qos_profile, true);
+ } else if(topic_endpoint_info_server.endpoint_type == RMW_ENDPOINT_SUBSCRIPTION) {
+ EXPECT_STREQ(topic_endpoint_info_server.topic_type, "test_msgs/srv/BasicTypes_Request");
+ assert_qos_equality(topic_endpoint_info_server.qos_profile, default_qos_profile, false);
+ }
+ }
+ // clean up
+ rmw_ret_t rmw_ret =
+ rmw_topic_endpoint_info_array_fini(&topic_endpoint_info_array_client, &allocator);
+ EXPECT_EQ(rmw_ret, RMW_RET_OK) << rmw_get_error_string().str;
+ rmw_ret = rmw_topic_endpoint_info_array_fini(&topic_endpoint_info_array_server, &allocator);
+ EXPECT_EQ(rmw_ret, RMW_RET_OK) << rmw_get_error_string().str;
+ ret = rcl_client_fini(&client, &this->node);
+ EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
+ ret = rcl_service_fini(&server, &this->node);
+ EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
+}