From 3cf8b546973314edd4f1b14a2413c659f3b87cb5 Mon Sep 17 00:00:00 2001 From: CY Chen Date: Thu, 4 Jun 2026 21:23:51 +0000 Subject: [PATCH 1/2] Add buffer backend metadata to topic endpoint info Signed-off-by: CY Chen --- rmw/include/rmw/topic_endpoint_info.h | 39 ++++++++++++++ rmw/src/topic_endpoint_info.c | 74 +++++++++++++++++++++++++++ rmw/test/test_topic_endpoint_info.cpp | 54 +++++++++++++++++++ 3 files changed, 167 insertions(+) diff --git a/rmw/include/rmw/topic_endpoint_info.h b/rmw/include/rmw/topic_endpoint_info.h index c05d397a..dcf1f730 100644 --- a/rmw/include/rmw/topic_endpoint_info.h +++ b/rmw/include/rmw/topic_endpoint_info.h @@ -21,6 +21,7 @@ extern "C" #endif #include "rcutils/allocator.h" +#include "rcutils/types/string_map.h" #include "rosidl_runtime_c/type_hash.h" #include "rmw/types.h" #include "rmw/visibility_control.h" @@ -44,6 +45,8 @@ typedef struct RMW_PUBLIC_TYPE rmw_topic_endpoint_info_s uint8_t endpoint_gid[RMW_GID_STORAGE_SIZE]; /// QoS profile of the endpoint rmw_qos_profile_t qos_profile; + /// Buffer backend metadata advertised for this endpoint + rcutils_string_map_t buffer_backend_metadata; } rmw_topic_endpoint_info_t; /// Return zero initialized topic endpoint info data structure. @@ -363,6 +366,42 @@ rmw_topic_endpoint_info_set_qos_profile( rmw_topic_endpoint_info_t * topic_endpoint_info, const rmw_qos_profile_t * qos_profile); +/// Set buffer backend metadata in the given topic endpoint info data structure. +/** + * Copies all key/value pairs from `buffer_backend_metadata` into the topic endpoint info. + * Keys are backend names and values are backend-specific metadata strings. + * + *
+ * Attribute | Adherence + * ------------------ | ------------- + * Allocates Memory | Yes + * Thread-Safe | No + * Uses Atomics | No + * Lock-Free | No + * + * \par Thread-safety + * Setting a member is a reentrant procedure, but access to the + * topic endpoint info data structure is not synchronized. + * It is not safe to read or write the `buffer_backend_metadata` member of the + * given `topic_endpoint` while setting it. + * + * \param[inout] topic_endpoint_info Data structure to be populated. + * \param[in] buffer_backend_metadata Buffer backend metadata to be copied. + * \param[in] allocator Allocator to be used. + * \returns `RMW_RET_OK` if successful, or + * \returns `RMW_RET_INVALID_ARGUMENT` if any arguments are invalid, or + * \returns `RMW_RET_BAD_ALLOC` if memory allocation fails, or + * \returns `RMW_RET_ERROR` when an unspecified error occurs. + * \remark This function sets the RMW error state on failure. + */ +RMW_PUBLIC +RMW_WARN_UNUSED +rmw_ret_t +rmw_topic_endpoint_info_set_buffer_backend_metadata( + rmw_topic_endpoint_info_t * topic_endpoint_info, + const rcutils_string_map_t * buffer_backend_metadata, + rcutils_allocator_t * allocator); + #ifdef __cplusplus } #endif diff --git a/rmw/src/topic_endpoint_info.c b/rmw/src/topic_endpoint_info.c index 10d819ae..3f7ee22d 100644 --- a/rmw/src/topic_endpoint_info.c +++ b/rmw/src/topic_endpoint_info.c @@ -15,7 +15,10 @@ #include "rmw/topic_endpoint_info.h" #include "rcutils/macros.h" +#include "rcutils/error_handling.h" #include "rcutils/strdup.h" +#include "rcutils/types/string_map.h" +#include "rmw/convert_rcutils_ret_to_rmw_ret.h" #include "rmw/error_handling.h" #include "rmw/types.h" @@ -90,6 +93,13 @@ rmw_topic_endpoint_info_fini( if (ret != RMW_RET_OK) { return ret; } + rcutils_ret_t rcutils_ret = + rcutils_string_map_fini(&topic_endpoint_info->buffer_backend_metadata); + if (rcutils_ret != RCUTILS_RET_OK) { + RMW_SET_ERROR_MSG(rcutils_get_error_string().str); + rcutils_reset_error(); + return rmw_convert_rcutils_ret_to_rmw_ret(rcutils_ret); + } *topic_endpoint_info = rmw_get_zero_initialized_topic_endpoint_info(); @@ -246,3 +256,67 @@ rmw_topic_endpoint_info_set_qos_profile( topic_endpoint_info->qos_profile = *qos_profile; return RMW_RET_OK; } + +rmw_ret_t +rmw_topic_endpoint_info_set_buffer_backend_metadata( + rmw_topic_endpoint_info_t * topic_endpoint_info, + const rcutils_string_map_t * buffer_backend_metadata, + rcutils_allocator_t * allocator) +{ + RCUTILS_CAN_RETURN_WITH_ERROR_OF(RMW_RET_INVALID_ARGUMENT); + RCUTILS_CAN_RETURN_WITH_ERROR_OF(RMW_RET_BAD_ALLOC); + + if (!topic_endpoint_info) { + RMW_SET_ERROR_MSG("topic_endpoint_info is null"); + return RMW_RET_INVALID_ARGUMENT; + } + if (!buffer_backend_metadata) { + RMW_SET_ERROR_MSG("buffer_backend_metadata is null"); + return RMW_RET_INVALID_ARGUMENT; + } + if (!allocator) { + RMW_SET_ERROR_MSG("allocator is null"); + return RMW_RET_INVALID_ARGUMENT; + } + + size_t size = 0; + rcutils_ret_t rcutils_ret = rcutils_string_map_get_size(buffer_backend_metadata, &size); + if (rcutils_ret != RCUTILS_RET_OK) { + RMW_SET_ERROR_MSG(rcutils_get_error_string().str); + rcutils_reset_error(); + return rmw_convert_rcutils_ret_to_rmw_ret(rcutils_ret); + } + + if (topic_endpoint_info->buffer_backend_metadata.impl) { + rcutils_ret = rcutils_string_map_fini(&topic_endpoint_info->buffer_backend_metadata); + if (rcutils_ret != RCUTILS_RET_OK) { + RMW_SET_ERROR_MSG(rcutils_get_error_string().str); + rcutils_reset_error(); + return rmw_convert_rcutils_ret_to_rmw_ret(rcutils_ret); + } + } + + topic_endpoint_info->buffer_backend_metadata = rcutils_get_zero_initialized_string_map(); + rcutils_ret = rcutils_string_map_init( + &topic_endpoint_info->buffer_backend_metadata, size, *allocator); + if (rcutils_ret != RCUTILS_RET_OK) { + RMW_SET_ERROR_MSG(rcutils_get_error_string().str); + rcutils_reset_error(); + return rmw_convert_rcutils_ret_to_rmw_ret(rcutils_ret); + } + + rcutils_ret = rcutils_string_map_copy( + buffer_backend_metadata, + &topic_endpoint_info->buffer_backend_metadata); + if (rcutils_ret != RCUTILS_RET_OK) { + rcutils_ret_t fini_ret = + rcutils_string_map_fini(&topic_endpoint_info->buffer_backend_metadata); + (void)fini_ret; + topic_endpoint_info->buffer_backend_metadata = rcutils_get_zero_initialized_string_map(); + RMW_SET_ERROR_MSG(rcutils_get_error_string().str); + rcutils_reset_error(); + return rmw_convert_rcutils_ret_to_rmw_ret(rcutils_ret); + } + + return RMW_RET_OK; +} diff --git a/rmw/test/test_topic_endpoint_info.cpp b/rmw/test/test_topic_endpoint_info.cpp index 2a3603d3..4f106bac 100644 --- a/rmw/test/test_topic_endpoint_info.cpp +++ b/rmw/test/test_topic_endpoint_info.cpp @@ -15,6 +15,7 @@ #include "gmock/gmock.h" #include "osrf_testing_tools_cpp/scope_exit.hpp" #include "rcutils/allocator.h" +#include "rcutils/types/string_map.h" #include "rmw/error_handling.h" #include "rmw/topic_endpoint_info.h" @@ -217,6 +218,47 @@ TEST(test_topic_endpoint_info, set_qos_profile) { false) << "Unequal avoid namespace conventions"; } +TEST(test_topic_endpoint_info, set_buffer_backend_metadata) { + rmw_topic_endpoint_info_t topic_endpoint_info = rmw_get_zero_initialized_topic_endpoint_info(); + rcutils_allocator_t allocator = rcutils_get_default_allocator(); + rcutils_string_map_t metadata = rcutils_get_zero_initialized_string_map(); + rcutils_ret_t rcutils_ret = rcutils_string_map_init(&metadata, 1, allocator); + ASSERT_EQ(rcutils_ret, RCUTILS_RET_OK); + OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT( + { + rcutils_ret_t fini_ret = rcutils_string_map_fini(&metadata); + EXPECT_EQ(fini_ret, RCUTILS_RET_OK); + }); + rcutils_ret = rcutils_string_map_set(&metadata, "cuda", "version=1.0"); + ASSERT_EQ(rcutils_ret, RCUTILS_RET_OK); + + rmw_ret_t ret = + rmw_topic_endpoint_info_set_buffer_backend_metadata(&topic_endpoint_info, &metadata, nullptr); + EXPECT_EQ(ret, RMW_RET_INVALID_ARGUMENT) << "Expected invalid argument for null allocator"; + rmw_reset_error(); + + ret = rmw_topic_endpoint_info_set_buffer_backend_metadata( + &topic_endpoint_info, nullptr, &allocator); + EXPECT_EQ(ret, RMW_RET_INVALID_ARGUMENT) << "Expected invalid argument for null metadata"; + rmw_reset_error(); + + ret = rmw_topic_endpoint_info_set_buffer_backend_metadata(nullptr, &metadata, &allocator); + EXPECT_EQ(ret, RMW_RET_INVALID_ARGUMENT) << + "Expected invalid argument for null topic_endpoint_info"; + rmw_reset_error(); + + ret = rmw_topic_endpoint_info_set_buffer_backend_metadata( + &topic_endpoint_info, &metadata, &allocator); + EXPECT_EQ(ret, RMW_RET_OK) << "Expected OK for valid arguments"; + EXPECT_STREQ( + rcutils_string_map_get(&topic_endpoint_info.buffer_backend_metadata, "cuda"), + "version=1.0"); + + ret = rmw_topic_endpoint_info_fini(&topic_endpoint_info, &allocator); + EXPECT_EQ(ret, RMW_RET_OK); + EXPECT_FALSE(topic_endpoint_info.buffer_backend_metadata.impl); +} + TEST(test_topic_endpoint_info, zero_init) { rmw_topic_endpoint_info_t topic_endpoint_info = rmw_get_zero_initialized_topic_endpoint_info(); EXPECT_FALSE(topic_endpoint_info.node_name); @@ -245,6 +287,7 @@ TEST(test_topic_endpoint_info, zero_init) { EXPECT_EQ( topic_endpoint_info.qos_profile.avoid_ros_namespace_conventions, false) << "Non-zero avoid namespace conventions"; + EXPECT_FALSE(topic_endpoint_info.buffer_backend_metadata.impl); } TEST(test_topic_endpoint_info, fini) { @@ -279,6 +322,16 @@ TEST(test_topic_endpoint_info, fini) { EXPECT_EQ(ret, RMW_RET_OK) << "Expected OK for valid node_name arguments"; ret = rmw_topic_endpoint_info_set_topic_type(&topic_endpoint_info, "type", &allocator); EXPECT_EQ(ret, RMW_RET_OK) << "Expected OK for valid topic_type arguments"; + rcutils_string_map_t metadata = rcutils_get_zero_initialized_string_map(); + rcutils_ret_t rcutils_ret = rcutils_string_map_init(&metadata, 1, allocator); + ASSERT_EQ(rcutils_ret, RCUTILS_RET_OK); + rcutils_ret = rcutils_string_map_set(&metadata, "cuda", ""); + ASSERT_EQ(rcutils_ret, RCUTILS_RET_OK); + ret = rmw_topic_endpoint_info_set_buffer_backend_metadata( + &topic_endpoint_info, &metadata, &allocator); + EXPECT_EQ(ret, RMW_RET_OK) << "Expected OK for valid buffer backend metadata"; + rcutils_ret = rcutils_string_map_fini(&metadata); + EXPECT_EQ(rcutils_ret, RCUTILS_RET_OK); ret = rmw_topic_endpoint_info_fini(&topic_endpoint_info, nullptr); EXPECT_EQ(ret, RMW_RET_INVALID_ARGUMENT) << "Expected invalid argument for null allocator"; rmw_reset_error(); @@ -314,4 +367,5 @@ TEST(test_topic_endpoint_info, fini) { "Non-zero liveliness lease duration nsec"; EXPECT_EQ(topic_endpoint_info.qos_profile.avoid_ros_namespace_conventions, false) << "Non-zero avoid namespace conventions"; + EXPECT_FALSE(topic_endpoint_info.buffer_backend_metadata.impl); } From 00844e51365f36fa81b3a06b8220db6e23671328 Mon Sep 17 00:00:00 2001 From: CY Chen Date: Wed, 1 Jul 2026 13:57:41 +0000 Subject: [PATCH 2/2] Use serialized strings for topic endpoint buffer metadata Signed-off-by: CY Chen --- .../rmw/impl/cpp/buffer_backend_metadata.hpp | 181 ++++++++++++++++++ rmw/include/rmw/topic_endpoint_info.h | 24 ++- rmw/src/topic_endpoint_info.c | 75 ++------ rmw/test/test_topic_endpoint_info.cpp | 73 ++++--- 4 files changed, 253 insertions(+), 100 deletions(-) create mode 100644 rmw/include/rmw/impl/cpp/buffer_backend_metadata.hpp diff --git a/rmw/include/rmw/impl/cpp/buffer_backend_metadata.hpp b/rmw/include/rmw/impl/cpp/buffer_backend_metadata.hpp new file mode 100644 index 00000000..efced698 --- /dev/null +++ b/rmw/include/rmw/impl/cpp/buffer_backend_metadata.hpp @@ -0,0 +1,181 @@ +// Copyright 2026 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__IMPL__CPP__BUFFER_BACKEND_METADATA_HPP_ +#define RMW__IMPL__CPP__BUFFER_BACKEND_METADATA_HPP_ + +#include +#include +#include +#include +#include +#include +#include + +namespace rmw +{ +namespace impl +{ +namespace cpp +{ + +inline constexpr char kBufferBackendMetadataPrefix[] = "backends:"; + +inline std::string +escape_buffer_backend_metadata_field(const std::string & input) +{ + std::string out; + out.reserve(input.size()); + for (char c : input) { + switch (c) { + case '%': + out += "%25"; + break; + case ';': + out += "%3B"; + break; + case ':': + out += "%3A"; + break; + case '/': + out += "%2F"; + break; + default: + out += c; + break; + } + } + return out; +} + +inline int +buffer_backend_metadata_hex_value(char c) +{ + if (c >= '0' && c <= '9') { + return c - '0'; + } + if (c >= 'A' && c <= 'F') { + return 10 + (c - 'A'); + } + if (c >= 'a' && c <= 'f') { + return 10 + (c - 'a'); + } + return -1; +} + +inline std::string +unescape_buffer_backend_metadata_field(const std::string_view input) +{ + std::string out; + out.reserve(input.size()); + for (size_t i = 0; i < input.size(); ++i) { + if (input[i] == '%' && i + 2 < input.size()) { + int hi = buffer_backend_metadata_hex_value(input[i + 1]); + int lo = buffer_backend_metadata_hex_value(input[i + 2]); + if (hi >= 0 && lo >= 0) { + out += static_cast((hi << 4) | lo); + i += 2; + continue; + } + } + out += input[i]; + } + return out; +} + +template +std::string +serialize_buffer_backend_metadata(const MapT & metadata) +{ + if (metadata.empty()) { + return {}; + } + + std::vector> entries; + entries.reserve(metadata.size()); + for (const auto & pair : metadata) { + if (!pair.first.empty()) { + entries.emplace_back(pair.first, pair.second); + } + } + if (entries.empty()) { + return {}; + } + + std::sort( + entries.begin(), entries.end(), + [](const auto & lhs, const auto & rhs) { + return lhs.first < rhs.first; + }); + + std::string output{kBufferBackendMetadataPrefix}; + for (size_t i = 0; i < entries.size(); ++i) { + output += escape_buffer_backend_metadata_field(entries[i].first); + output += ":"; + output += escape_buffer_backend_metadata_field(entries[i].second); + if (i + 1 < entries.size()) { + output += ";"; + } + } + return output; +} + +inline std::map +parse_buffer_backend_metadata(const char * serialized_metadata) +{ + std::map result; + if (!serialized_metadata || serialized_metadata[0] == '\0') { + return result; + } + + std::string_view input{serialized_metadata}; + constexpr std::string_view prefix{kBufferBackendMetadataPrefix}; + if (input.rfind(prefix, 0) != 0) { + return result; + } + + std::string_view body = input.substr(prefix.size()); + if (body.empty()) { + return result; + } + + size_t start = 0; + while (start <= body.size()) { + size_t sep = body.find(';', start); + std::string_view entry = body.substr( + start, sep == std::string_view::npos ? std::string_view::npos : sep - start); + if (!entry.empty()) { + size_t colon = entry.find(':'); + std::string name = unescape_buffer_backend_metadata_field(entry.substr(0, colon)); + std::string metadata; + if (colon != std::string_view::npos) { + metadata = unescape_buffer_backend_metadata_field(entry.substr(colon + 1)); + } + if (!name.empty()) { + result[std::move(name)] = std::move(metadata); + } + } + if (sep == std::string_view::npos) { + break; + } + start = sep + 1; + } + return result; +} + +} // namespace cpp +} // namespace impl +} // namespace rmw + +#endif // RMW__IMPL__CPP__BUFFER_BACKEND_METADATA_HPP_ diff --git a/rmw/include/rmw/topic_endpoint_info.h b/rmw/include/rmw/topic_endpoint_info.h index dcf1f730..6dec1d07 100644 --- a/rmw/include/rmw/topic_endpoint_info.h +++ b/rmw/include/rmw/topic_endpoint_info.h @@ -21,7 +21,6 @@ extern "C" #endif #include "rcutils/allocator.h" -#include "rcutils/types/string_map.h" #include "rosidl_runtime_c/type_hash.h" #include "rmw/types.h" #include "rmw/visibility_control.h" @@ -45,8 +44,8 @@ typedef struct RMW_PUBLIC_TYPE rmw_topic_endpoint_info_s uint8_t endpoint_gid[RMW_GID_STORAGE_SIZE]; /// QoS profile of the endpoint rmw_qos_profile_t qos_profile; - /// Buffer backend metadata advertised for this endpoint - rcutils_string_map_t buffer_backend_metadata; + /// Serialized buffer backend metadata advertised for this endpoint + const char * buffer_backend_metadata; } rmw_topic_endpoint_info_t; /// Return zero initialized topic endpoint info data structure. @@ -366,10 +365,10 @@ rmw_topic_endpoint_info_set_qos_profile( rmw_topic_endpoint_info_t * topic_endpoint_info, const rmw_qos_profile_t * qos_profile); -/// Set buffer backend metadata in the given topic endpoint info data structure. +/// Set serialized buffer backend metadata in the given topic endpoint info data structure. /** - * Copies all key/value pairs from `buffer_backend_metadata` into the topic endpoint info. - * Keys are backend names and values are backend-specific metadata strings. + * Allocates memory and copies the serialized `buffer_backend_metadata` string + * into the topic endpoint info. * *
* Attribute | Adherence @@ -377,19 +376,24 @@ rmw_topic_endpoint_info_set_qos_profile( * Allocates Memory | Yes * Thread-Safe | No * Uses Atomics | No - * Lock-Free | No + * Lock-Free | Yes * * \par Thread-safety * Setting a member is a reentrant procedure, but access to the * topic endpoint info data structure is not synchronized. * It is not safe to read or write the `buffer_backend_metadata` member of the * given `topic_endpoint` while setting it. + * Access to C-style string arguments is read-only but it is not synchronized. + * Concurrent `buffer_backend_metadata` reads are safe, but concurrent reads and writes are not. + * + * \pre Given `buffer_backend_metadata` is a valid C-style string i.e. NULL terminated. * * \param[inout] topic_endpoint_info Data structure to be populated. - * \param[in] buffer_backend_metadata Buffer backend metadata to be copied. + * \param[in] buffer_backend_metadata Serialized buffer backend metadata to be set. * \param[in] allocator Allocator to be used. * \returns `RMW_RET_OK` if successful, or - * \returns `RMW_RET_INVALID_ARGUMENT` if any arguments are invalid, or + * \returns `RMW_RET_INVALID_ARGUMENT` if `topic_endpoint_info` is NULL, or + * \returns `RMW_RET_INVALID_ARGUMENT` if `buffer_backend_metadata` is NULL, or * \returns `RMW_RET_BAD_ALLOC` if memory allocation fails, or * \returns `RMW_RET_ERROR` when an unspecified error occurs. * \remark This function sets the RMW error state on failure. @@ -399,7 +403,7 @@ RMW_WARN_UNUSED rmw_ret_t rmw_topic_endpoint_info_set_buffer_backend_metadata( rmw_topic_endpoint_info_t * topic_endpoint_info, - const rcutils_string_map_t * buffer_backend_metadata, + const char * buffer_backend_metadata, rcutils_allocator_t * allocator); #ifdef __cplusplus diff --git a/rmw/src/topic_endpoint_info.c b/rmw/src/topic_endpoint_info.c index 3f7ee22d..f772eb29 100644 --- a/rmw/src/topic_endpoint_info.c +++ b/rmw/src/topic_endpoint_info.c @@ -15,10 +15,7 @@ #include "rmw/topic_endpoint_info.h" #include "rcutils/macros.h" -#include "rcutils/error_handling.h" #include "rcutils/strdup.h" -#include "rcutils/types/string_map.h" -#include "rmw/convert_rcutils_ret_to_rmw_ret.h" #include "rmw/error_handling.h" #include "rmw/types.h" @@ -64,6 +61,15 @@ _rmw_topic_endpoint_info_fini_topic_type( return _rmw_topic_endpoint_info_fini_str(&topic_endpoint_info->topic_type, allocator); } +rmw_ret_t +_rmw_topic_endpoint_info_fini_buffer_backend_metadata( + rmw_topic_endpoint_info_t * topic_endpoint_info, + rcutils_allocator_t * allocator) +{ + return _rmw_topic_endpoint_info_fini_str( + &topic_endpoint_info->buffer_backend_metadata, allocator); +} + rmw_ret_t rmw_topic_endpoint_info_fini( rmw_topic_endpoint_info_t * topic_endpoint_info, @@ -93,12 +99,9 @@ rmw_topic_endpoint_info_fini( if (ret != RMW_RET_OK) { return ret; } - rcutils_ret_t rcutils_ret = - rcutils_string_map_fini(&topic_endpoint_info->buffer_backend_metadata); - if (rcutils_ret != RCUTILS_RET_OK) { - RMW_SET_ERROR_MSG(rcutils_get_error_string().str); - rcutils_reset_error(); - return rmw_convert_rcutils_ret_to_rmw_ret(rcutils_ret); + ret = _rmw_topic_endpoint_info_fini_buffer_backend_metadata(topic_endpoint_info, allocator); + if (ret != RMW_RET_OK) { + return ret; } *topic_endpoint_info = rmw_get_zero_initialized_topic_endpoint_info(); @@ -260,63 +263,17 @@ rmw_topic_endpoint_info_set_qos_profile( rmw_ret_t rmw_topic_endpoint_info_set_buffer_backend_metadata( rmw_topic_endpoint_info_t * topic_endpoint_info, - const rcutils_string_map_t * buffer_backend_metadata, + const char * buffer_backend_metadata, rcutils_allocator_t * allocator) { RCUTILS_CAN_RETURN_WITH_ERROR_OF(RMW_RET_INVALID_ARGUMENT); - RCUTILS_CAN_RETURN_WITH_ERROR_OF(RMW_RET_BAD_ALLOC); if (!topic_endpoint_info) { RMW_SET_ERROR_MSG("topic_endpoint_info is null"); return RMW_RET_INVALID_ARGUMENT; } - if (!buffer_backend_metadata) { - RMW_SET_ERROR_MSG("buffer_backend_metadata is null"); - return RMW_RET_INVALID_ARGUMENT; - } - if (!allocator) { - RMW_SET_ERROR_MSG("allocator is null"); - return RMW_RET_INVALID_ARGUMENT; - } - - size_t size = 0; - rcutils_ret_t rcutils_ret = rcutils_string_map_get_size(buffer_backend_metadata, &size); - if (rcutils_ret != RCUTILS_RET_OK) { - RMW_SET_ERROR_MSG(rcutils_get_error_string().str); - rcutils_reset_error(); - return rmw_convert_rcutils_ret_to_rmw_ret(rcutils_ret); - } - - if (topic_endpoint_info->buffer_backend_metadata.impl) { - rcutils_ret = rcutils_string_map_fini(&topic_endpoint_info->buffer_backend_metadata); - if (rcutils_ret != RCUTILS_RET_OK) { - RMW_SET_ERROR_MSG(rcutils_get_error_string().str); - rcutils_reset_error(); - return rmw_convert_rcutils_ret_to_rmw_ret(rcutils_ret); - } - } - - topic_endpoint_info->buffer_backend_metadata = rcutils_get_zero_initialized_string_map(); - rcutils_ret = rcutils_string_map_init( - &topic_endpoint_info->buffer_backend_metadata, size, *allocator); - if (rcutils_ret != RCUTILS_RET_OK) { - RMW_SET_ERROR_MSG(rcutils_get_error_string().str); - rcutils_reset_error(); - return rmw_convert_rcutils_ret_to_rmw_ret(rcutils_ret); - } - - rcutils_ret = rcutils_string_map_copy( + return _rmw_topic_endpoint_info_copy_str( + &topic_endpoint_info->buffer_backend_metadata, buffer_backend_metadata, - &topic_endpoint_info->buffer_backend_metadata); - if (rcutils_ret != RCUTILS_RET_OK) { - rcutils_ret_t fini_ret = - rcutils_string_map_fini(&topic_endpoint_info->buffer_backend_metadata); - (void)fini_ret; - topic_endpoint_info->buffer_backend_metadata = rcutils_get_zero_initialized_string_map(); - RMW_SET_ERROR_MSG(rcutils_get_error_string().str); - rcutils_reset_error(); - return rmw_convert_rcutils_ret_to_rmw_ret(rcutils_ret); - } - - return RMW_RET_OK; + allocator); } diff --git a/rmw/test/test_topic_endpoint_info.cpp b/rmw/test/test_topic_endpoint_info.cpp index 4f106bac..09bf4d4b 100644 --- a/rmw/test/test_topic_endpoint_info.cpp +++ b/rmw/test/test_topic_endpoint_info.cpp @@ -12,12 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include +#include +#include +#include + #include "gmock/gmock.h" #include "osrf_testing_tools_cpp/scope_exit.hpp" #include "rcutils/allocator.h" -#include "rcutils/types/string_map.h" - #include "rmw/error_handling.h" +#include "rmw/impl/cpp/buffer_backend_metadata.hpp" #include "rmw/topic_endpoint_info.h" #include "rmw/types.h" @@ -221,19 +225,10 @@ TEST(test_topic_endpoint_info, set_qos_profile) { TEST(test_topic_endpoint_info, set_buffer_backend_metadata) { rmw_topic_endpoint_info_t topic_endpoint_info = rmw_get_zero_initialized_topic_endpoint_info(); rcutils_allocator_t allocator = rcutils_get_default_allocator(); - rcutils_string_map_t metadata = rcutils_get_zero_initialized_string_map(); - rcutils_ret_t rcutils_ret = rcutils_string_map_init(&metadata, 1, allocator); - ASSERT_EQ(rcutils_ret, RCUTILS_RET_OK); - OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT( - { - rcutils_ret_t fini_ret = rcutils_string_map_fini(&metadata); - EXPECT_EQ(fini_ret, RCUTILS_RET_OK); - }); - rcutils_ret = rcutils_string_map_set(&metadata, "cuda", "version=1.0"); - ASSERT_EQ(rcutils_ret, RCUTILS_RET_OK); + char * val = get_mallocd_string("backends:test:version=1.0"); rmw_ret_t ret = - rmw_topic_endpoint_info_set_buffer_backend_metadata(&topic_endpoint_info, &metadata, nullptr); + rmw_topic_endpoint_info_set_buffer_backend_metadata(&topic_endpoint_info, val, nullptr); EXPECT_EQ(ret, RMW_RET_INVALID_ARGUMENT) << "Expected invalid argument for null allocator"; rmw_reset_error(); @@ -242,21 +237,44 @@ TEST(test_topic_endpoint_info, set_buffer_backend_metadata) { EXPECT_EQ(ret, RMW_RET_INVALID_ARGUMENT) << "Expected invalid argument for null metadata"; rmw_reset_error(); - ret = rmw_topic_endpoint_info_set_buffer_backend_metadata(nullptr, &metadata, &allocator); + ret = rmw_topic_endpoint_info_set_buffer_backend_metadata(nullptr, val, &allocator); EXPECT_EQ(ret, RMW_RET_INVALID_ARGUMENT) << "Expected invalid argument for null topic_endpoint_info"; rmw_reset_error(); + OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT( + { + allocator.deallocate( + const_cast(topic_endpoint_info.buffer_backend_metadata), + allocator.state); + }); ret = rmw_topic_endpoint_info_set_buffer_backend_metadata( - &topic_endpoint_info, &metadata, &allocator); + &topic_endpoint_info, val, &allocator); EXPECT_EQ(ret, RMW_RET_OK) << "Expected OK for valid arguments"; - EXPECT_STREQ( - rcutils_string_map_get(&topic_endpoint_info.buffer_backend_metadata, "cuda"), - "version=1.0"); + free(val); + EXPECT_STREQ(topic_endpoint_info.buffer_backend_metadata, "backends:test:version=1.0"); +} - ret = rmw_topic_endpoint_info_fini(&topic_endpoint_info, &allocator); - EXPECT_EQ(ret, RMW_RET_OK); - EXPECT_FALSE(topic_endpoint_info.buffer_backend_metadata.impl); +TEST(test_topic_endpoint_info, buffer_backend_metadata_serialization) { + std::map metadata{ + {"test", "version=1.0"}, + {"cpu", ""}, + {"with:colon", "semi;slash/percent%"}, + }; + + const std::string serialized = + rmw::impl::cpp::serialize_buffer_backend_metadata(metadata); + EXPECT_EQ( + serialized, + "backends:cpu:;test:version=1.0;with%3Acolon:semi%3Bslash%2Fpercent%25"); + + EXPECT_EQ(rmw::impl::cpp::parse_buffer_backend_metadata(serialized.c_str()), metadata); + EXPECT_TRUE( + rmw::impl::cpp::serialize_buffer_backend_metadata( + std::map{}).empty()); + EXPECT_TRUE(rmw::impl::cpp::parse_buffer_backend_metadata(nullptr).empty()); + EXPECT_TRUE(rmw::impl::cpp::parse_buffer_backend_metadata("").empty()); + EXPECT_TRUE(rmw::impl::cpp::parse_buffer_backend_metadata("not-backends:cpu:").empty()); } TEST(test_topic_endpoint_info, zero_init) { @@ -287,7 +305,7 @@ TEST(test_topic_endpoint_info, zero_init) { EXPECT_EQ( topic_endpoint_info.qos_profile.avoid_ros_namespace_conventions, false) << "Non-zero avoid namespace conventions"; - EXPECT_FALSE(topic_endpoint_info.buffer_backend_metadata.impl); + EXPECT_FALSE(topic_endpoint_info.buffer_backend_metadata); } TEST(test_topic_endpoint_info, fini) { @@ -322,16 +340,9 @@ TEST(test_topic_endpoint_info, fini) { EXPECT_EQ(ret, RMW_RET_OK) << "Expected OK for valid node_name arguments"; ret = rmw_topic_endpoint_info_set_topic_type(&topic_endpoint_info, "type", &allocator); EXPECT_EQ(ret, RMW_RET_OK) << "Expected OK for valid topic_type arguments"; - rcutils_string_map_t metadata = rcutils_get_zero_initialized_string_map(); - rcutils_ret_t rcutils_ret = rcutils_string_map_init(&metadata, 1, allocator); - ASSERT_EQ(rcutils_ret, RCUTILS_RET_OK); - rcutils_ret = rcutils_string_map_set(&metadata, "cuda", ""); - ASSERT_EQ(rcutils_ret, RCUTILS_RET_OK); ret = rmw_topic_endpoint_info_set_buffer_backend_metadata( - &topic_endpoint_info, &metadata, &allocator); + &topic_endpoint_info, "backends:test:", &allocator); EXPECT_EQ(ret, RMW_RET_OK) << "Expected OK for valid buffer backend metadata"; - rcutils_ret = rcutils_string_map_fini(&metadata); - EXPECT_EQ(rcutils_ret, RCUTILS_RET_OK); ret = rmw_topic_endpoint_info_fini(&topic_endpoint_info, nullptr); EXPECT_EQ(ret, RMW_RET_INVALID_ARGUMENT) << "Expected invalid argument for null allocator"; rmw_reset_error(); @@ -367,5 +378,5 @@ TEST(test_topic_endpoint_info, fini) { "Non-zero liveliness lease duration nsec"; EXPECT_EQ(topic_endpoint_info.qos_profile.avoid_ros_namespace_conventions, false) << "Non-zero avoid namespace conventions"; - EXPECT_FALSE(topic_endpoint_info.buffer_backend_metadata.impl); + EXPECT_FALSE(topic_endpoint_info.buffer_backend_metadata); }