-
Notifications
You must be signed in to change notification settings - Fork 163
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Convert TypeDescription to YAML and hash
Signed-off-by: Emerson Knapp <[email protected]>
- Loading branch information
1 parent
c5e07c1
commit 96a8486
Showing
6 changed files
with
561 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.