Skip to content

Commit

Permalink
Convert TypeDescription to YAML and hash
Browse files Browse the repository at this point in the history
Signed-off-by: Emerson Knapp <[email protected]>
  • Loading branch information
emersonknapp committed Mar 13, 2023
1 parent c5e07c1 commit 96a8486
Show file tree
Hide file tree
Showing 6 changed files with 561 additions and 0 deletions.
4 changes: 4 additions & 0 deletions rcl/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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)
Expand Down
76 changes: 76 additions & 0 deletions rcl/include/rcl/type_version_hash.h
Original file line number Diff line number Diff line change
@@ -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_
1 change: 1 addition & 0 deletions rcl/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<depend>rosidl_runtime_c</depend>
<depend>service_msgs</depend>
<depend>tracetools</depend>
<depend>type_description_interfaces</depend>

<test_depend>ament_cmake_gtest</test_depend>
<test_depend>ament_lint_auto</test_depend>
Expand Down
262 changes: 262 additions & 0 deletions rcl/src/rcl/type_version_hash.c
Original file line number Diff line number Diff line change
@@ -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 <yaml.h>

#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;
}
6 changes: 6 additions & 0 deletions rcl/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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}
)
Loading

0 comments on commit 96a8486

Please sign in to comment.