From c44ac92eeaf0dac12140cd65e690280f07479967 Mon Sep 17 00:00:00 2001 From: Emerson Knapp Date: Mon, 16 Jan 2023 19:14:28 -0800 Subject: [PATCH 1/8] Convert TypeDescription to YAML and hash Signed-off-by: Emerson Knapp --- rcl/CMakeLists.txt | 4 + rcl/include/rcl/type_version_hash.h | 76 +++++++ rcl/package.xml | 1 + rcl/src/rcl/type_version_hash.c | 262 ++++++++++++++++++++++++ rcl/test/CMakeLists.txt | 6 + rcl/test/rcl/test_type_version_hash.cpp | 212 +++++++++++++++++++ 6 files changed, 561 insertions(+) create mode 100644 rcl/include/rcl/type_version_hash.h create mode 100644 rcl/src/rcl/type_version_hash.c create mode 100644 rcl/test/rcl/test_type_version_hash.cpp diff --git a/rcl/CMakeLists.txt b/rcl/CMakeLists.txt index a2aa1d65a..fbb876521 100644 --- a/rcl/CMakeLists.txt +++ b/rcl/CMakeLists.txt @@ -13,6 +13,7 @@ find_package(rmw_implementation REQUIRED) find_package(rosidl_runtime_c REQUIRED) find_package(service_msgs REQUIRED) find_package(tracetools REQUIRED) +find_package(type_description_interfaces REQUIRED) include(cmake/rcl_set_symbol_visibility_hidden.cmake) include(cmake/get_default_rcl_logging_implementation.cmake) @@ -64,6 +65,7 @@ set(${PROJECT_NAME}_sources src/rcl/subscription.c src/rcl/time.c src/rcl/timer.c + src/rcl/type_version_hash.c src/rcl/validate_enclave_name.c src/rcl/validate_topic_name.c src/rcl/wait.c @@ -86,6 +88,7 @@ ament_target_dependencies(${PROJECT_NAME} "rosidl_runtime_c" "service_msgs" "tracetools" + "type_description_interfaces" ) # Causes the visibility macros to use dllexport rather than dllimport, @@ -127,6 +130,7 @@ ament_export_dependencies(${RCL_LOGGING_IMPL}) ament_export_dependencies(rosidl_runtime_c) ament_export_dependencies(service_msgs) ament_export_dependencies(tracetools) +ament_export_dependencies(type_description_interfaces) if(BUILD_TESTING) find_package(ament_lint_auto REQUIRED) diff --git a/rcl/include/rcl/type_version_hash.h b/rcl/include/rcl/type_version_hash.h new file mode 100644 index 000000000..1cb58123e --- /dev/null +++ b/rcl/include/rcl/type_version_hash.h @@ -0,0 +1,76 @@ +// Copyright 2023 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 RCL__TYPE_VERSION_HASH_H_ +#define RCL__TYPE_VERSION_HASH_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "rcl/types.h" +#include "rcl/visibility_control.h" +#include "rcutils/sha256.h" +#include "rosidl_runtime_c/type_hash.h" +#include "type_description_interfaces/msg/type_description.h" + +/// Given a TypeDescription, output a string of the hashable JSON representation of that data. +/** + * The output here is generally hashed via rcl_calculate_type_version_hash below. + * Compare this reference implementation with rosidl_parser.type_hash.idl_to_hashable_json. + * Both must produce the same output for the same types, providing a stable reference for + * external implementations of the ROS 2 Type Version Hash. + * + * The JSON representation contains all types and fields of the original message, but excludes: + * - Default values + * - Comments + * - The input plaintext files that generated the TypeDescription + * + * \param[in] type_description Prefilled TypeDescription message to be translated + * \param[out] output_repr An initialized empty char array that will be filled with + * the JSON representation of type_description. Note that output_repr will have a + * terminating null character, which should be omitted from hashing. To do so, use + * (output_repr.buffer_length - 1) or strlen(output_repr.buffer) for the size of data to hash. + * \return RCL_RET_OK on success, RCL_RET_ERROR if any problems occur in translation + */ +RCL_PUBLIC +rcl_ret_t +rcl_type_description_to_hashable_json( + const type_description_interfaces__msg__TypeDescription * type_description, + rcutils_char_array_t * output_repr); + +/// Calculate the Type Version Hash for a given TypeDescription. +/** + * This function produces a stable hash value for a ROS communication interface type. + * For design motivations leading to this implementation, see REP-2011. + * + * This convenience wrapper calls rcl_type_description_to_hashable_json, + * then runs sha256 hash on the result + * + * \param[in] msg Prefilled TypeDescription message describing the type to be hashed + * \param[out] message_digest Preallocated buffer, to be filled with calculated checksum + * \return RCL_RET_OK on success, RCL_RET_ERROR if any problems occur while hashing + */ +RCL_PUBLIC +rcl_ret_t +rcl_calculate_type_version_hash( + const type_description_interfaces__msg__TypeDescription * type_description, + rosidl_type_hash_t * out_type_hash); + +#ifdef __cplusplus +} +#endif + +#endif // RCL__TYPE_VERSION_HASH_H_ diff --git a/rcl/package.xml b/rcl/package.xml index 41adb55d0..720146f4e 100644 --- a/rcl/package.xml +++ b/rcl/package.xml @@ -28,6 +28,7 @@ rosidl_runtime_c service_msgs tracetools + type_description_interfaces ament_cmake_gtest ament_lint_auto diff --git a/rcl/src/rcl/type_version_hash.c b/rcl/src/rcl/type_version_hash.c new file mode 100644 index 000000000..ed4e11c04 --- /dev/null +++ b/rcl/src/rcl/type_version_hash.c @@ -0,0 +1,262 @@ +// Copyright 2023 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "rcl/allocator.h" +#include "rcl/error_handling.h" +#include "rcl/type_version_hash.h" +#include "rcutils/types/char_array.h" +#include "rcutils/sha256.h" +#include "type_description_interfaces/msg/type_description.h" + +static int yaml_write_handler(void * ext, uint8_t * buffer, size_t size) +{ + rcutils_char_array_t * repr = (rcutils_char_array_t *)ext; + rcutils_ret_t res = rcutils_char_array_strncat(repr, (char *)buffer, size); + return res == RCL_RET_OK ? 1 : 0; +} + +static inline int start_sequence(yaml_emitter_t * emitter) +{ + yaml_event_t event; + return + yaml_sequence_start_event_initialize(&event, NULL, NULL, 1, YAML_FLOW_SEQUENCE_STYLE) && + yaml_emitter_emit(emitter, &event); +} + +static inline int end_sequence(yaml_emitter_t * emitter) +{ + yaml_event_t event; + return + yaml_sequence_end_event_initialize(&event) && + yaml_emitter_emit(emitter, &event); +} + +static inline int start_mapping(yaml_emitter_t * emitter) +{ + yaml_event_t event; + return + yaml_mapping_start_event_initialize(&event, NULL, NULL, 1, YAML_FLOW_MAPPING_STYLE) && + yaml_emitter_emit(emitter, &event); +} + +static inline int end_mapping(yaml_emitter_t * emitter) +{ + yaml_event_t event; + return + yaml_mapping_end_event_initialize(&event) && + yaml_emitter_emit(emitter, &event); +} + +static int emit_key(yaml_emitter_t * emitter, const char * key) +{ + yaml_event_t event; + return + yaml_scalar_event_initialize( + &event, NULL, NULL, (yaml_char_t *)key, strlen(key), 0, 1, YAML_DOUBLE_QUOTED_SCALAR_STYLE) && + yaml_emitter_emit(emitter, &event); +} + +static int emit_int(yaml_emitter_t * emitter, size_t val, const char * fmt) +{ + // longest uint64 is 20 decimal digits, plus one byte for trailing \0 + char decimal_buf[21]; + yaml_event_t event; + int ret = snprintf(decimal_buf, sizeof(decimal_buf), fmt, val); + if (ret < 0) { + emitter->problem = "Failed expanding integer"; + return 0; + } + if ((size_t)ret >= sizeof(decimal_buf)) { + emitter->problem = "Decimal buffer overflow"; + return 0; + } + return + yaml_scalar_event_initialize( + &event, NULL, NULL, + (yaml_char_t *)decimal_buf, strlen(decimal_buf), + 1, 0, YAML_PLAIN_SCALAR_STYLE) && + yaml_emitter_emit(emitter, &event); +} + +static int emit_str(yaml_emitter_t * emitter, const rosidl_runtime_c__String * val) +{ + yaml_event_t event; + return + yaml_scalar_event_initialize( + &event, NULL, NULL, + (yaml_char_t *)val->data, val->size, + 0, 1, YAML_DOUBLE_QUOTED_SCALAR_STYLE) && + yaml_emitter_emit(emitter, &event); +} + +static int emit_field_type( + yaml_emitter_t * emitter, + const type_description_interfaces__msg__FieldType * field_type) +{ + return + start_mapping(emitter) && + + emit_key(emitter, "type_id") && + emit_int(emitter, field_type->type_id, "%d") && + + emit_key(emitter, "capacity") && + emit_int(emitter, field_type->capacity, "%zu") && + + emit_key(emitter, "string_capacity") && + emit_int(emitter, field_type->string_capacity, "%zu") && + + emit_key(emitter, "nested_type_name") && + emit_str(emitter, &field_type->nested_type_name) && + + end_mapping(emitter); +} + +static int emit_field( + yaml_emitter_t * emitter, + const type_description_interfaces__msg__Field * field) +{ + return + start_mapping(emitter) && + + emit_key(emitter, "name") && + emit_str(emitter, &field->name) && + + emit_key(emitter, "type") && + emit_field_type(emitter, &field->type) && + + end_mapping(emitter); +} + +static int emit_individual_type_description( + yaml_emitter_t * emitter, + const type_description_interfaces__msg__IndividualTypeDescription * individual_type_description) +{ + if (!( + start_mapping(emitter) && + + emit_key(emitter, "type_name") && + emit_str(emitter, &individual_type_description->type_name) && + + emit_key(emitter, "fields") && + start_sequence(emitter))) + { + return 0; + } + for (size_t i = 0; i < individual_type_description->fields.size; i++) { + if (!emit_field(emitter, &individual_type_description->fields.data[i])) { + return 0; + } + } + return end_sequence(emitter) && end_mapping(emitter); +} + +static int emit_type_description( + yaml_emitter_t * emitter, + const type_description_interfaces__msg__TypeDescription * type_description) +{ + if (!( + start_mapping(emitter) && + + emit_key(emitter, "type_description") && + emit_individual_type_description(emitter, &type_description->type_description) && + + emit_key(emitter, "referenced_type_descriptions") && + start_sequence(emitter))) + { + return 0; + } + for (size_t i = 0; i < type_description->referenced_type_descriptions.size; i++) { + if (!emit_individual_type_description( + emitter, &type_description->referenced_type_descriptions.data[i])) + { + return 0; + } + } + return end_sequence(emitter) && end_mapping(emitter); +} + +rcl_ret_t +rcl_type_description_to_hashable_json( + const type_description_interfaces__msg__TypeDescription * type_description, + rcutils_char_array_t * output_repr) +{ + RCL_CHECK_ARGUMENT_FOR_NULL(type_description, RCL_RET_INVALID_ARGUMENT); + RCL_CHECK_ARGUMENT_FOR_NULL(output_repr, RCL_RET_INVALID_ARGUMENT); + + yaml_emitter_t emitter; + yaml_event_t event; + + if (!yaml_emitter_initialize(&emitter)) { + goto error; + } + + // Disable line breaks based on line length + yaml_emitter_set_width(&emitter, -1); + // Circumvent EOF line break by providing invalid break style + yaml_emitter_set_break(&emitter, -1); + yaml_emitter_set_output(&emitter, yaml_write_handler, output_repr); + + if (!( + yaml_stream_start_event_initialize(&event, YAML_UTF8_ENCODING) && + yaml_emitter_emit(&emitter, &event) && + + yaml_document_start_event_initialize(&event, NULL, NULL, NULL, 1) && + yaml_emitter_emit(&emitter, &event) && + + emit_type_description(&emitter, type_description) && + + yaml_document_end_event_initialize(&event, 1) && + yaml_emitter_emit(&emitter, &event) && + + yaml_stream_end_event_initialize(&event) && + yaml_emitter_emit(&emitter, &event))) + { + goto error; + } + + yaml_emitter_delete(&emitter); + return RCL_RET_OK; + +error: + rcl_set_error_state(emitter.problem, __FILE__, __LINE__); + yaml_emitter_delete(&emitter); + return RCL_RET_ERROR; +} + +rcl_ret_t +rcl_calculate_type_version_hash( + const type_description_interfaces__msg__TypeDescription * type_description, + rosidl_type_hash_t * output_hash) +{ + RCL_CHECK_ARGUMENT_FOR_NULL(type_description, RCL_RET_INVALID_ARGUMENT); + RCL_CHECK_ARGUMENT_FOR_NULL(output_hash, RCL_RET_INVALID_ARGUMENT); + + rcl_ret_t result = RCL_RET_OK; + rcutils_char_array_t msg_repr = rcutils_get_zero_initialized_char_array(); + msg_repr.allocator = rcl_get_default_allocator(); + + output_hash->version = 1; + result = rcl_type_description_to_hashable_json(type_description, &msg_repr); + if (result == RCL_RET_OK) { + rcutils_sha256_ctx_t sha_ctx; + rcutils_sha256_init(&sha_ctx); + // Last item in char_array is null terminator, which should not be hashed. + rcutils_sha256_update(&sha_ctx, (const uint8_t *)msg_repr.buffer, msg_repr.buffer_length - 1); + rcutils_sha256_final(&sha_ctx, output_hash->value); + } + result = rcutils_char_array_fini(&msg_repr); + return result; +} diff --git a/rcl/test/CMakeLists.txt b/rcl/test/CMakeLists.txt index 743270fda..551f5a893 100644 --- a/rcl/test/CMakeLists.txt +++ b/rcl/test/CMakeLists.txt @@ -456,3 +456,9 @@ rcl_add_custom_gtest(test_subscription_content_filter_options LIBRARIES ${PROJECT_NAME} AMENT_DEPENDENCIES "osrf_testing_tools_cpp" "test_msgs" ) + +rcl_add_custom_gtest(test_type_version_hash + SRCS rcl/test_type_version_hash.cpp + APPEND_LIBRARY_DIRS ${extra_lib_dirs} + LIBRARIES ${PROJECT_NAME} +) diff --git a/rcl/test/rcl/test_type_version_hash.cpp b/rcl/test/rcl/test_type_version_hash.cpp new file mode 100644 index 000000000..d1a03b6d8 --- /dev/null +++ b/rcl/test/rcl/test_type_version_hash.cpp @@ -0,0 +1,212 @@ +// Copyright 2023 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "type_description_interfaces/msg/type_description.h" +#include "type_description_interfaces/msg/individual_type_description.h" +#include "type_description_interfaces/msg/field.h" +#include "rosidl_runtime_c/string_functions.h" +#include "rcl/allocator.h" +#include "rcl/type_version_hash.h" + +// Copied directly from generated code +static const rosidl_type_hash_t sensor_msgs__msg__PointCloud2__TYPE_VERSION_HASH__copy = {1, { + 0xaf, 0x49, 0x7e, 0x7c, 0x94, 0x3f, 0xf7, 0xf8, + 0xdb, 0x1d, 0x50, 0x1d, 0xb0, 0x9d, 0x0a, 0xad, + 0x44, 0x12, 0x4e, 0xd7, 0xdf, 0x43, 0xc1, 0xe8, + 0x6c, 0x64, 0x26, 0x02, 0xd0, 0x6c, 0xbb, 0x7c, +}}; + +static void init_individual_type_description( + type_description_interfaces__msg__IndividualTypeDescription * itd, + const char * name, + const std::vector> & fields) +{ + rosidl_runtime_c__String__assign( + &itd->type_name, name); + type_description_interfaces__msg__Field__Sequence__init(&itd->fields, fields.size()); + for (size_t i = 0; i < fields.size(); i++) { + auto * field = &itd->fields.data[i]; + const char * name = std::get<0>(fields[i]); + + rosidl_runtime_c__String__assign(&field->name, name); + field->type.type_id = std::get<1>(fields[i]); + const char * nested_type = std::get<2>(fields[i]); + if (nested_type != NULL) { + rosidl_runtime_c__String__assign(&field->type.nested_type_name, nested_type); + } + } +} + +TEST(TestTypeVersionHash, smoke_check) { + rcl_ret_t res = RCL_RET_OK; + type_description_interfaces__msg__TypeDescription * td_msg = + type_description_interfaces__msg__TypeDescription__create(); + + init_individual_type_description( + &td_msg->type_description, + "type_description_interfaces/msg/FieldType", { + {"type_id", type_description_interfaces__msg__FieldType__FIELD_TYPE_UINT8, NULL}, + {"length", type_description_interfaces__msg__FieldType__FIELD_TYPE_UINT64, NULL}, + {"string_length", type_description_interfaces__msg__FieldType__FIELD_TYPE_UINT64, NULL}, + {"nested_type_name", type_description_interfaces__msg__FieldType__FIELD_TYPE_STRING, NULL}} + ); + + rosidl_type_hash_t direct_hash; + res = rcl_calculate_type_version_hash(td_msg, &direct_hash); + ASSERT_EQ(res, RCL_RET_OK); + + rosidl_type_hash_t hash_from_repr; + hash_from_repr.version = 1; + { + rcutils_sha256_ctx_t sha; + rcutils_char_array_t msg_repr = rcutils_get_zero_initialized_char_array(); + msg_repr.allocator = rcl_get_default_allocator(); + + res = rcl_type_description_to_hashable_json(td_msg, &msg_repr); + ASSERT_EQ(res, RCL_RET_OK); + + rcutils_sha256_init(&sha); + rcutils_sha256_update(&sha, (const uint8_t *)msg_repr.buffer, msg_repr.buffer_length - 1); + rcutils_sha256_final(&sha, hash_from_repr.value); + + res = rcutils_char_array_fini(&msg_repr); + ASSERT_EQ(res, RCL_RET_OK); + } + + // NOTE: testing this against the actual installed one, forces an up to date test + auto validation_hash = type_description_interfaces__msg__FieldType__TYPE_VERSION_HASH; + ASSERT_EQ(direct_hash.version, hash_from_repr.version); + ASSERT_EQ(direct_hash.version, validation_hash.version); + ASSERT_EQ(0, memcmp(direct_hash.value, hash_from_repr.value, ROSIDL_TYPE_HASH_SIZE)); + ASSERT_EQ(0, memcmp(direct_hash.value, validation_hash.value, ROSIDL_TYPE_HASH_SIZE)); +} + + +TEST(TestTypeVersionHash, nested_types) { + rcl_ret_t res = RCL_RET_OK; + type_description_interfaces__msg__TypeDescription * td_msg = + type_description_interfaces__msg__TypeDescription__create(); + // 3 referenced types: std_msgs/Header, builtin_interfaces/Time, sensor_msgs/PointField + type_description_interfaces__msg__IndividualTypeDescription__Sequence__init( + &td_msg->referenced_type_descriptions, 3); + + // PointCloud2.msg + // + // std_msgs/Header header + // uint32 height + // uint32 width + // PointField[] fields + // bool is_bigendian + // uint32 point_step + // uint32 row_step + // uint8[] data + // bool is_dense + init_individual_type_description( + &td_msg->type_description, + "sensor_msgs/msg/PointCloud2", { + { + "header", + type_description_interfaces__msg__FieldType__FIELD_TYPE_NESTED_TYPE, + "std_msgs/msg/Header"}, + {"height", type_description_interfaces__msg__FieldType__FIELD_TYPE_UINT32, NULL}, + {"width", type_description_interfaces__msg__FieldType__FIELD_TYPE_UINT32, NULL}, + { + "fields", + type_description_interfaces__msg__FieldType__FIELD_TYPE_NESTED_TYPE_UNBOUNDED_SEQUENCE, + "sensor_msgs/msg/PointField"}, + {"is_bigendian", type_description_interfaces__msg__FieldType__FIELD_TYPE_BOOLEAN, NULL}, + {"point_step", type_description_interfaces__msg__FieldType__FIELD_TYPE_UINT32, NULL}, + {"row_step", type_description_interfaces__msg__FieldType__FIELD_TYPE_UINT32, NULL}, + { + "data", + type_description_interfaces__msg__FieldType__FIELD_TYPE_UINT8_UNBOUNDED_SEQUENCE, + NULL}, + {"is_dense", type_description_interfaces__msg__FieldType__FIELD_TYPE_BOOLEAN, NULL}} + ); + + // add referenced types in alphabetical order + size_t nested_field_index = 0; + + // builtin_interfaces/msg/Time.msg + // + // int32 sec + // uint32 nanosec + init_individual_type_description( + &td_msg->referenced_type_descriptions.data[nested_field_index++], + "builtin_interfaces/msg/Time", { + {"sec", type_description_interfaces__msg__FieldType__FIELD_TYPE_INT32, NULL}, + {"nanosec", type_description_interfaces__msg__FieldType__FIELD_TYPE_UINT32, NULL}} + ); + + // sensor_msgs/msg/PointField.msg + // + // string name + // uint32 offset + // uint8 datatype + // uint32 count + init_individual_type_description( + &td_msg->referenced_type_descriptions.data[nested_field_index++], + "sensor_msgs/msg/PointField", { + {"name", type_description_interfaces__msg__FieldType__FIELD_TYPE_STRING, NULL}, + {"offset", type_description_interfaces__msg__FieldType__FIELD_TYPE_UINT32, NULL}, + {"datatype", type_description_interfaces__msg__FieldType__FIELD_TYPE_UINT8, NULL}, + {"count", type_description_interfaces__msg__FieldType__FIELD_TYPE_UINT32, NULL}} + ); + + // std_msgs/msg/Header.msg + // + // builtin_interfaces/Time stamp + // string frame_id + init_individual_type_description( + &td_msg->referenced_type_descriptions.data[nested_field_index++], + "std_msgs/msg/Header", { + { + "stamp", + type_description_interfaces__msg__FieldType__FIELD_TYPE_NESTED_TYPE, + "builtin_interfaces/msg/Time"}, + { + "frame_id", + type_description_interfaces__msg__FieldType__FIELD_TYPE_STRING, + NULL}} + ); + + rosidl_type_hash_t direct_hash; + res = rcl_calculate_type_version_hash(td_msg, &direct_hash); + ASSERT_EQ(res, RCL_RET_OK); + + rosidl_type_hash_t hash_from_repr; + { + rcutils_sha256_ctx_t sha; + rcutils_char_array_t msg_repr = rcutils_get_zero_initialized_char_array(); + msg_repr.allocator = rcl_get_default_allocator(); + + res = rcl_type_description_to_hashable_json(td_msg, &msg_repr); + ASSERT_EQ(res, RCL_RET_OK); + + rcutils_sha256_init(&sha); + rcutils_sha256_update(&sha, (const uint8_t *)msg_repr.buffer, msg_repr.buffer_length - 1); + rcutils_sha256_final(&sha, hash_from_repr.value); + + res = rcutils_char_array_fini(&msg_repr); + ASSERT_EQ(res, RCL_RET_OK); + } + + auto validation_hash = sensor_msgs__msg__PointCloud2__TYPE_VERSION_HASH__copy; + ASSERT_EQ(direct_hash.version, hash_from_repr.version); + ASSERT_EQ(direct_hash.version, validation_hash.version); + ASSERT_EQ(0, memcmp(direct_hash.value, hash_from_repr.value, ROSIDL_TYPE_HASH_SIZE)); + ASSERT_EQ(0, memcmp(direct_hash.value, validation_hash.value, ROSIDL_TYPE_HASH_SIZE)); +} From 9d745da38859b4abc8395c8db950e4b377db5e4d Mon Sep 17 00:00:00 2001 From: Emerson Knapp Date: Wed, 22 Mar 2023 01:43:51 -0700 Subject: [PATCH 2/8] Update to latest Signed-off-by: Emerson Knapp --- rcl/CMakeLists.txt | 2 +- .../rcl/{type_version_hash.h => type_hash.h} | 10 ++--- .../rcl/{type_version_hash.c => type_hash.c} | 4 +- rcl/test/CMakeLists.txt | 4 +- ...pe_version_hash.cpp => test_type_hash.cpp} | 38 +++++++++++-------- 5 files changed, 32 insertions(+), 26 deletions(-) rename rcl/include/rcl/{type_version_hash.h => type_hash.h} (92%) rename rcl/src/rcl/{type_version_hash.c => type_hash.c} (99%) rename rcl/test/rcl/{test_type_version_hash.cpp => test_type_hash.cpp} (88%) diff --git a/rcl/CMakeLists.txt b/rcl/CMakeLists.txt index fbb876521..30339a3c8 100644 --- a/rcl/CMakeLists.txt +++ b/rcl/CMakeLists.txt @@ -65,7 +65,7 @@ set(${PROJECT_NAME}_sources src/rcl/subscription.c src/rcl/time.c src/rcl/timer.c - src/rcl/type_version_hash.c + src/rcl/type_hash.c src/rcl/validate_enclave_name.c src/rcl/validate_topic_name.c src/rcl/wait.c diff --git a/rcl/include/rcl/type_version_hash.h b/rcl/include/rcl/type_hash.h similarity index 92% rename from rcl/include/rcl/type_version_hash.h rename to rcl/include/rcl/type_hash.h index 1cb58123e..e41058fee 100644 --- a/rcl/include/rcl/type_version_hash.h +++ b/rcl/include/rcl/type_hash.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef RCL__TYPE_VERSION_HASH_H_ -#define RCL__TYPE_VERSION_HASH_H_ +#ifndef RCL__TYPE_HASH_H_ +#define RCL__TYPE_HASH_H_ #ifdef __cplusplus extern "C" @@ -28,7 +28,7 @@ extern "C" /// Given a TypeDescription, output a string of the hashable JSON representation of that data. /** - * The output here is generally hashed via rcl_calculate_type_version_hash below. + * The output here is generally hashed via rcl_calculate_type_hash below. * Compare this reference implementation with rosidl_parser.type_hash.idl_to_hashable_json. * Both must produce the same output for the same types, providing a stable reference for * external implementations of the ROS 2 Type Version Hash. @@ -65,7 +65,7 @@ rcl_type_description_to_hashable_json( */ RCL_PUBLIC rcl_ret_t -rcl_calculate_type_version_hash( +rcl_calculate_type_hash( const type_description_interfaces__msg__TypeDescription * type_description, rosidl_type_hash_t * out_type_hash); @@ -73,4 +73,4 @@ rcl_calculate_type_version_hash( } #endif -#endif // RCL__TYPE_VERSION_HASH_H_ +#endif // RCL__TYPE_HASH_H_ diff --git a/rcl/src/rcl/type_version_hash.c b/rcl/src/rcl/type_hash.c similarity index 99% rename from rcl/src/rcl/type_version_hash.c rename to rcl/src/rcl/type_hash.c index ed4e11c04..faf78d21f 100644 --- a/rcl/src/rcl/type_version_hash.c +++ b/rcl/src/rcl/type_hash.c @@ -16,7 +16,7 @@ #include "rcl/allocator.h" #include "rcl/error_handling.h" -#include "rcl/type_version_hash.h" +#include "rcl/type_hash.h" #include "rcutils/types/char_array.h" #include "rcutils/sha256.h" #include "type_description_interfaces/msg/type_description.h" @@ -237,7 +237,7 @@ rcl_type_description_to_hashable_json( } rcl_ret_t -rcl_calculate_type_version_hash( +rcl_calculate_type_hash( const type_description_interfaces__msg__TypeDescription * type_description, rosidl_type_hash_t * output_hash) { diff --git a/rcl/test/CMakeLists.txt b/rcl/test/CMakeLists.txt index 551f5a893..560f0540d 100644 --- a/rcl/test/CMakeLists.txt +++ b/rcl/test/CMakeLists.txt @@ -457,8 +457,8 @@ rcl_add_custom_gtest(test_subscription_content_filter_options AMENT_DEPENDENCIES "osrf_testing_tools_cpp" "test_msgs" ) -rcl_add_custom_gtest(test_type_version_hash - SRCS rcl/test_type_version_hash.cpp +rcl_add_custom_gtest(test_type_hash + SRCS rcl/test_type_hash.cpp APPEND_LIBRARY_DIRS ${extra_lib_dirs} LIBRARIES ${PROJECT_NAME} ) diff --git a/rcl/test/rcl/test_type_version_hash.cpp b/rcl/test/rcl/test_type_hash.cpp similarity index 88% rename from rcl/test/rcl/test_type_version_hash.cpp rename to rcl/test/rcl/test_type_hash.cpp index d1a03b6d8..05e9bd07b 100644 --- a/rcl/test/rcl/test_type_version_hash.cpp +++ b/rcl/test/rcl/test_type_hash.cpp @@ -19,15 +19,16 @@ #include "type_description_interfaces/msg/field.h" #include "rosidl_runtime_c/string_functions.h" #include "rcl/allocator.h" -#include "rcl/type_version_hash.h" +#include "rcl/type_hash.h" // Copied directly from generated code -static const rosidl_type_hash_t sensor_msgs__msg__PointCloud2__TYPE_VERSION_HASH__copy = {1, { - 0xaf, 0x49, 0x7e, 0x7c, 0x94, 0x3f, 0xf7, 0xf8, - 0xdb, 0x1d, 0x50, 0x1d, 0xb0, 0x9d, 0x0a, 0xad, - 0x44, 0x12, 0x4e, 0xd7, 0xdf, 0x43, 0xc1, 0xe8, - 0x6c, 0x64, 0x26, 0x02, 0xd0, 0x6c, 0xbb, 0x7c, -}}; +static const rosidl_type_hash_t sensor_msgs__msg__PointCloud2__TYPE_HASH__copy = {1, { + 0x91, 0x98, 0xca, 0xbf, 0x7d, 0xa3, 0x79, 0x6a, + 0xe6, 0xfe, 0x19, 0xc4, 0xcb, 0x3b, 0xdd, 0x35, + 0x25, 0x49, 0x29, 0x88, 0xc7, 0x05, 0x22, 0x62, + 0x8a, 0xf5, 0xda, 0xa1, 0x24, 0xba, 0xe2, 0xb5, + }}; + static void init_individual_type_description( type_description_interfaces__msg__IndividualTypeDescription * itd, @@ -50,7 +51,7 @@ static void init_individual_type_description( } } -TEST(TestTypeVersionHash, smoke_check) { +TEST(TestTypeVersionHash, field_type_from_install) { rcl_ret_t res = RCL_RET_OK; type_description_interfaces__msg__TypeDescription * td_msg = type_description_interfaces__msg__TypeDescription__create(); @@ -59,13 +60,17 @@ TEST(TestTypeVersionHash, smoke_check) { &td_msg->type_description, "type_description_interfaces/msg/FieldType", { {"type_id", type_description_interfaces__msg__FieldType__FIELD_TYPE_UINT8, NULL}, - {"length", type_description_interfaces__msg__FieldType__FIELD_TYPE_UINT64, NULL}, - {"string_length", type_description_interfaces__msg__FieldType__FIELD_TYPE_UINT64, NULL}, - {"nested_type_name", type_description_interfaces__msg__FieldType__FIELD_TYPE_STRING, NULL}} + {"capacity", type_description_interfaces__msg__FieldType__FIELD_TYPE_UINT64, NULL}, + {"string_capacity", type_description_interfaces__msg__FieldType__FIELD_TYPE_UINT64, NULL}, + { + "nested_type_name", + type_description_interfaces__msg__FieldType__FIELD_TYPE_BOUNDED_STRING, + NULL}} ); + td_msg->type_description.fields.data[3].type.string_capacity = 255; rosidl_type_hash_t direct_hash; - res = rcl_calculate_type_version_hash(td_msg, &direct_hash); + res = rcl_calculate_type_hash(td_msg, &direct_hash); ASSERT_EQ(res, RCL_RET_OK); rosidl_type_hash_t hash_from_repr; @@ -87,7 +92,7 @@ TEST(TestTypeVersionHash, smoke_check) { } // NOTE: testing this against the actual installed one, forces an up to date test - auto validation_hash = type_description_interfaces__msg__FieldType__TYPE_VERSION_HASH; + auto validation_hash = type_description_interfaces__msg__FieldType__TYPE_HASH; ASSERT_EQ(direct_hash.version, hash_from_repr.version); ASSERT_EQ(direct_hash.version, validation_hash.version); ASSERT_EQ(0, memcmp(direct_hash.value, hash_from_repr.value, ROSIDL_TYPE_HASH_SIZE)); @@ -95,7 +100,7 @@ TEST(TestTypeVersionHash, smoke_check) { } -TEST(TestTypeVersionHash, nested_types) { +TEST(TestTypeVersionHash, nested_real_type) { rcl_ret_t res = RCL_RET_OK; type_description_interfaces__msg__TypeDescription * td_msg = type_description_interfaces__msg__TypeDescription__create(); @@ -184,10 +189,11 @@ TEST(TestTypeVersionHash, nested_types) { ); rosidl_type_hash_t direct_hash; - res = rcl_calculate_type_version_hash(td_msg, &direct_hash); + res = rcl_calculate_type_hash(td_msg, &direct_hash); ASSERT_EQ(res, RCL_RET_OK); rosidl_type_hash_t hash_from_repr; + hash_from_repr.version = 1; { rcutils_sha256_ctx_t sha; rcutils_char_array_t msg_repr = rcutils_get_zero_initialized_char_array(); @@ -204,7 +210,7 @@ TEST(TestTypeVersionHash, nested_types) { ASSERT_EQ(res, RCL_RET_OK); } - auto validation_hash = sensor_msgs__msg__PointCloud2__TYPE_VERSION_HASH__copy; + auto validation_hash = sensor_msgs__msg__PointCloud2__TYPE_HASH__copy; ASSERT_EQ(direct_hash.version, hash_from_repr.version); ASSERT_EQ(direct_hash.version, validation_hash.version); ASSERT_EQ(0, memcmp(direct_hash.value, hash_from_repr.value, ROSIDL_TYPE_HASH_SIZE)); From b1c1bbc8ce724edf0a33fabbf13bf2dcb388bec7 Mon Sep 17 00:00:00 2001 From: Emerson Knapp Date: Wed, 22 Mar 2023 01:58:46 -0700 Subject: [PATCH 3/8] Address review nits Signed-off-by: Emerson Knapp --- rcl/include/rcl/type_hash.h | 2 +- rcl/src/rcl/type_hash.c | 2 ++ rcl/test/rcl/test_type_hash.cpp | 5 +++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/rcl/include/rcl/type_hash.h b/rcl/include/rcl/type_hash.h index e41058fee..242a37bcd 100644 --- a/rcl/include/rcl/type_hash.h +++ b/rcl/include/rcl/type_hash.h @@ -57,7 +57,7 @@ rcl_type_description_to_hashable_json( * For design motivations leading to this implementation, see REP-2011. * * This convenience wrapper calls rcl_type_description_to_hashable_json, - * then runs sha256 hash on the result + * then runs sha256 hash on the result. * * \param[in] msg Prefilled TypeDescription message describing the type to be hashed * \param[out] message_digest Preallocated buffer, to be filled with calculated checksum diff --git a/rcl/src/rcl/type_hash.c b/rcl/src/rcl/type_hash.c index faf78d21f..1eca6e887 100644 --- a/rcl/src/rcl/type_hash.c +++ b/rcl/src/rcl/type_hash.c @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include + #include #include "rcl/allocator.h" diff --git a/rcl/test/rcl/test_type_hash.cpp b/rcl/test/rcl/test_type_hash.cpp index 05e9bd07b..f9b4df78c 100644 --- a/rcl/test/rcl/test_type_hash.cpp +++ b/rcl/test/rcl/test_type_hash.cpp @@ -14,12 +14,17 @@ #include +#include +#include +#include + #include "type_description_interfaces/msg/type_description.h" #include "type_description_interfaces/msg/individual_type_description.h" #include "type_description_interfaces/msg/field.h" #include "rosidl_runtime_c/string_functions.h" #include "rcl/allocator.h" #include "rcl/type_hash.h" +#include "rcutils/sha256.h" // Copied directly from generated code static const rosidl_type_hash_t sensor_msgs__msg__PointCloud2__TYPE_HASH__copy = {1, { From febd2ff621885ad26e895d6950ca68a1be69ef86 Mon Sep 17 00:00:00 2001 From: Emerson Knapp Date: Thu, 23 Mar 2023 11:44:41 -0700 Subject: [PATCH 4/8] Explicit yaml dependency and doctext update Signed-off-by: Emerson Knapp --- rcl/CMakeLists.txt | 3 +++ rcl/include/rcl/type_hash.h | 6 ++++-- rcl/package.xml | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/rcl/CMakeLists.txt b/rcl/CMakeLists.txt index 30339a3c8..9f4803295 100644 --- a/rcl/CMakeLists.txt +++ b/rcl/CMakeLists.txt @@ -4,6 +4,7 @@ project(rcl) find_package(ament_cmake_ros REQUIRED) +find_package(libyaml_vendor REQUIRED) find_package(rcl_interfaces REQUIRED) find_package(rcl_logging_interface REQUIRED) find_package(rcl_yaml_param_parser REQUIRED) @@ -14,6 +15,7 @@ find_package(rosidl_runtime_c REQUIRED) find_package(service_msgs REQUIRED) find_package(tracetools REQUIRED) find_package(type_description_interfaces REQUIRED) +find_package(yaml REQUIRED) include(cmake/rcl_set_symbol_visibility_hidden.cmake) include(cmake/get_default_rcl_logging_implementation.cmake) @@ -89,6 +91,7 @@ ament_target_dependencies(${PROJECT_NAME} "service_msgs" "tracetools" "type_description_interfaces" + "yaml" ) # Causes the visibility macros to use dllexport rather than dllimport, diff --git a/rcl/include/rcl/type_hash.h b/rcl/include/rcl/type_hash.h index 242a37bcd..9570c25f1 100644 --- a/rcl/include/rcl/type_hash.h +++ b/rcl/include/rcl/type_hash.h @@ -43,7 +43,8 @@ extern "C" * the JSON representation of type_description. Note that output_repr will have a * terminating null character, which should be omitted from hashing. To do so, use * (output_repr.buffer_length - 1) or strlen(output_repr.buffer) for the size of data to hash. - * \return RCL_RET_OK on success, RCL_RET_ERROR if any problems occur in translation + * \return #RCL_RET_OK on success, or + * \return #RCL_RET_ERROR if any problems occur in translation. */ RCL_PUBLIC rcl_ret_t @@ -61,7 +62,8 @@ rcl_type_description_to_hashable_json( * * \param[in] msg Prefilled TypeDescription message describing the type to be hashed * \param[out] message_digest Preallocated buffer, to be filled with calculated checksum - * \return RCL_RET_OK on success, RCL_RET_ERROR if any problems occur while hashing + * \return #RCL_RET_OK on success, or + * \return #RCL_RET_ERROR if any problems occur while hashing. */ RCL_PUBLIC rcl_ret_t diff --git a/rcl/package.xml b/rcl/package.xml index 720146f4e..e0c065563 100644 --- a/rcl/package.xml +++ b/rcl/package.xml @@ -19,6 +19,7 @@ rmw + libyaml_vendor rcl_interfaces rcl_logging_interface rcl_logging_spdlog @@ -29,6 +30,7 @@ service_msgs tracetools type_description_interfaces + yaml ament_cmake_gtest ament_lint_auto From 454adac62c6d19f2b00d863ad7aae55ed0492456 Mon Sep 17 00:00:00 2001 From: Emerson Knapp Date: Mon, 27 Mar 2023 14:33:51 -0700 Subject: [PATCH 5/8] Update docstring language Signed-off-by: Emerson Knapp --- rcl/include/rcl/type_hash.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rcl/include/rcl/type_hash.h b/rcl/include/rcl/type_hash.h index 9570c25f1..08ac509ce 100644 --- a/rcl/include/rcl/type_hash.h +++ b/rcl/include/rcl/type_hash.h @@ -28,8 +28,9 @@ extern "C" /// Given a TypeDescription, output a string of the hashable JSON representation of that data. /** - * The output here is generally hashed via rcl_calculate_type_hash below. - * Compare this reference implementation with rosidl_parser.type_hash.idl_to_hashable_json. + * The output here is generally hashed via rcl_calculate_type_hash() below. + * Compare this reference implementation with the .json output files from + * `rosidl_generator_type_description.generate_type_hash`. * Both must produce the same output for the same types, providing a stable reference for * external implementations of the ROS 2 Type Version Hash. * From fb40e7ca4c9c310e2c737cd4831986454429e591 Mon Sep 17 00:00:00 2001 From: Emerson Knapp Date: Mon, 27 Mar 2023 21:14:58 -0700 Subject: [PATCH 6/8] Fix ament exported dependency Signed-off-by: Emerson Knapp --- rcl/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rcl/CMakeLists.txt b/rcl/CMakeLists.txt index 9f4803295..5881331a7 100644 --- a/rcl/CMakeLists.txt +++ b/rcl/CMakeLists.txt @@ -80,6 +80,7 @@ target_include_directories(${PROJECT_NAME} PUBLIC "$") # specific order: dependents before dependencies ament_target_dependencies(${PROJECT_NAME} + "libyaml_vendor" "rcl_interfaces" "rcl_logging_interface" "rcl_yaml_param_parser" @@ -91,7 +92,6 @@ ament_target_dependencies(${PROJECT_NAME} "service_msgs" "tracetools" "type_description_interfaces" - "yaml" ) # Causes the visibility macros to use dllexport rather than dllimport, From c761578e8f44de223e45ce3b4d218880b105b0d1 Mon Sep 17 00:00:00 2001 From: Emerson Knapp Date: Tue, 28 Mar 2023 12:01:30 -0700 Subject: [PATCH 7/8] Ament export dependency Signed-off-by: Emerson Knapp --- rcl/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/rcl/CMakeLists.txt b/rcl/CMakeLists.txt index 5881331a7..d09ad1122 100644 --- a/rcl/CMakeLists.txt +++ b/rcl/CMakeLists.txt @@ -134,6 +134,7 @@ ament_export_dependencies(rosidl_runtime_c) ament_export_dependencies(service_msgs) ament_export_dependencies(tracetools) ament_export_dependencies(type_description_interfaces) +ament_export_dependencies(libyaml_vendor) if(BUILD_TESTING) find_package(ament_lint_auto REQUIRED) From 00dbb574368ee237c5bc97861d03c44b65c4cddf Mon Sep 17 00:00:00 2001 From: Emerson Knapp Date: Tue, 28 Mar 2023 21:15:47 -0700 Subject: [PATCH 8/8] Fix msbuild warnings Signed-off-by: Emerson Knapp --- rcl/src/rcl/type_hash.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/rcl/src/rcl/type_hash.c b/rcl/src/rcl/type_hash.c index 1eca6e887..f05dd9851 100644 --- a/rcl/src/rcl/type_hash.c +++ b/rcl/src/rcl/type_hash.c @@ -67,7 +67,9 @@ static int emit_key(yaml_emitter_t * emitter, const char * key) yaml_event_t event; return yaml_scalar_event_initialize( - &event, NULL, NULL, (yaml_char_t *)key, strlen(key), 0, 1, YAML_DOUBLE_QUOTED_SCALAR_STYLE) && + &event, NULL, NULL, + (yaml_char_t *)key, (int)strlen(key), + 0, 1, YAML_DOUBLE_QUOTED_SCALAR_STYLE) && yaml_emitter_emit(emitter, &event); } @@ -88,7 +90,7 @@ static int emit_int(yaml_emitter_t * emitter, size_t val, const char * fmt) return yaml_scalar_event_initialize( &event, NULL, NULL, - (yaml_char_t *)decimal_buf, strlen(decimal_buf), + (yaml_char_t *)decimal_buf, (int)strlen(decimal_buf), 1, 0, YAML_PLAIN_SCALAR_STYLE) && yaml_emitter_emit(emitter, &event); } @@ -99,7 +101,7 @@ static int emit_str(yaml_emitter_t * emitter, const rosidl_runtime_c__String * v return yaml_scalar_event_initialize( &event, NULL, NULL, - (yaml_char_t *)val->data, val->size, + (yaml_char_t *)val->data, (int)val->size, 0, 1, YAML_DOUBLE_QUOTED_SCALAR_STYLE) && yaml_emitter_emit(emitter, &event); }