diff --git a/rosidl_runtime_c/CMakeLists.txt b/rosidl_runtime_c/CMakeLists.txt index a3cbbbd36..74351ae75 100644 --- a/rosidl_runtime_c/CMakeLists.txt +++ b/rosidl_runtime_c/CMakeLists.txt @@ -25,6 +25,7 @@ add_library(${PROJECT_NAME} "src/string_functions.c" "src/type_hash.c" "src/u16string_functions.c" + "src/type_description_utils.c" ${type_description_sources} ) target_include_directories(${PROJECT_NAME} PUBLIC @@ -136,6 +137,11 @@ if(BUILD_TESTING) target_compile_definitions(test_u16string_functions PUBLIC RCUTILS_ENABLE_FAULT_INJECTION) endif() + ament_add_gtest(test_type_description_utils test/test_type_description_utils.cpp) + if(TARGET test_type_description_utils) + target_link_libraries(test_type_description_utils ${PROJECT_NAME}) + endif() + add_performance_test(benchmark_string_conversion test/benchmark/benchmark_string_conversion.cpp) if(TARGET benchmark_string_conversion) target_link_libraries(benchmark_string_conversion ${PROJECT_NAME}) diff --git a/rosidl_runtime_c/include/rosidl_runtime_c/type_description_utils.h b/rosidl_runtime_c/include/rosidl_runtime_c/type_description_utils.h new file mode 100644 index 000000000..b190a9330 --- /dev/null +++ b/rosidl_runtime_c/include/rosidl_runtime_c/type_description_utils.h @@ -0,0 +1,656 @@ +// 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. + +/** + * \file Utilities for manipulating type_description_interfaces C message structs. + * + * Every instance of a non-message struct (e.g. hash map) borrows, whereas the message structs copy. + * Hence, lifetime should be managed by the message structs. + */ + +#ifndef ROSIDL_RUNTIME_C__TYPE_DESCRIPTION_UTILS_H_ +#define ROSIDL_RUNTIME_C__TYPE_DESCRIPTION_UTILS_H_ + +#include +#include +#include + +#include + +#include +#include + +#include "rosidl_runtime_c/type_description/field__functions.h" +#include "rosidl_runtime_c/type_description/field__struct.h" +#include "rosidl_runtime_c/type_description/individual_type_description__functions.h" +#include "rosidl_runtime_c/type_description/individual_type_description__struct.h" +#include "rosidl_runtime_c/type_description/type_description__functions.h" +#include "rosidl_runtime_c/type_description/type_description__struct.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +static const uint8_t ROSIDL_RUNTIME_C_TYPE_DESCRIPTION_UTILS_SEQUENCE_TYPE_ID_MASK = 48; + +// ================================================================================================= +// GET BY NAME +// ================================================================================================= + +/// Get the first Field, matching by name. +/** + * The `field` output arg must be passed in pointing to `NULL`. + * It will remain pointing to `NULL` if no matching `Field` is found. + * + * Ownership: + * - The output `Field` borrows the `fields` arg's `Field` element. It is not authorized to + * deallocate it and cannot outlive the owner it borrows from. + * + *
+ * Attribute | Adherence + * ------------------ | ------------- + * Allocates Memory | No + * Thread-Safe | No + * Uses Atomics | No + * Lock-Free | Yes + * + * \param[in] fields array of fields to search through + * \param[in] name field name of the field to look for + * \param[out] field the first field with field name that matches the name arg. + * Must point to `NULL`, outputs pointing to `NULL` if not found. + * \return #RCUTILS_RET_OK if successful, or + * \return #RCUTILS_RET_INVALID_ARGUMENT for invalid arguments, or + * \return #RCUTILS_RET_NOT_FOUND if no `Field` with a matching name is found, or + * \return #RCUTILS_RET_ERROR if an unknown error occurs. + */ +ROSIDL_GENERATOR_C_PUBLIC +rcutils_ret_t +rosidl_runtime_c_type_description_utils_find_field( + const rosidl_runtime_c__type_description__Field__Sequence * fields, + const char * name, + rosidl_runtime_c__type_description__Field ** field); + +/// Get the first referenced IndividualTypeDescription, matching by type name. +/** + * The `referenced_type` output arg must be passed in pointing `NULL`. + * It will remain pointing to `NULL` if no matching `IndividualTypeDescription` is found. + * + * Ownership: + * - The output `IndividualTypeDescription` borrows the `referenced_types` arg's + * `IndividualTypeDescription` element. + * It is not authorized to deallocate it and it cannot outlive the `referenced_types` it borrows + * from. + * + *
+ * Attribute | Adherence + * ------------------ | ------------- + * Allocates Memory | No + * Thread-Safe | No + * Uses Atomics | No + * Lock-Free | Yes + * + * \param[in] referenced_types array of referenced individual type descriptions to search through + * \param[in] type_name name of the referenced referenced individual type description to look for + * \param[out] referenced_type the first individual type description with type name that matches the + * type_name arg. Must point to `NULL`, outputs pointing to `NULL` if not found. + * \return #RCUTILS_RET_OK if successful, or + * \return #RCUTILS_RET_INVALID_ARGUMENT for invalid arguments, or + * \return #RCUTILS_RET_NOT_FOUND if no `IndividualTypeDescription` with matching name is found, or + * \return #RCUTILS_RET_ERROR if an unknown error occurs. + */ +ROSIDL_GENERATOR_C_PUBLIC +rcutils_ret_t +rosidl_runtime_c_type_description_utils_find_referenced_type_description( + const rosidl_runtime_c__type_description__IndividualTypeDescription__Sequence * referenced_types, + const char * type_name, + rosidl_runtime_c__type_description__IndividualTypeDescription ** referenced_type); + + +// ================================================================================================= +// HASH MAPS +// ================================================================================================= + +/// Construct a hash map of an `IndividualTypeDescription`'s `Field` objects, keyed by field name. +/** + * NOTE: The `hash_map` output arg must be passed in pointing to `NULL`. + * + * Ownership: + * - The caller assumes ownership of the output `rcutils_hash_map_t` and must free it and its + * elements, but NOT finalize its values. + * - The output `rcutils_hash_map_t` has values that borrows the `individual_description` arg's + * `Field` objects. It is not authorized to deallocate them and it cannot outlive the owner it + * borrows from. + * - Finalizing the `rcutils_hash_map_t` should not result in the map's values getting finalized. + * + *
+ * Attribute | Adherence + * ------------------ | ------------- + * Allocates Memory | Yes + * Thread-Safe | No + * Uses Atomics | No + * Lock-Free | Yes + * + * \param[in] individual_description the individual type description to get the fields from + * \param[in] allocator the allocator to use through out the lifetime of the hash_map + * \param[out] hash_map `rcutils_hash_map_t` to be initialized, containing `Field` objects keyed by + * their field names. Must point to `NULL`, outputs pointing to `NULL` if error. + * \return #RCUTILS_RET_OK if successful, or + * \return #RCUTILS_RET_BAD_ALLOC if memory allocation fails, or + * \return #RCUTILS_RET_INVALID_ARGUMENT for invalid arguments, or + * \return #RCUTILS_RET_ERROR if an unknown error occurs. + */ +ROSIDL_GENERATOR_C_PUBLIC +rcutils_ret_t +rosidl_runtime_c_type_description_utils_get_field_map( + const rosidl_runtime_c__type_description__IndividualTypeDescription * individual_description, + const rcutils_allocator_t * allocator, + rcutils_hash_map_t ** hash_map); + +/// Construct a hash map of an `IndividualTypeDescription__Sequence`'s `IndividualTypeDescription` +/// objects, keyed by type name. +/** + * The `hash_map` output arg must be passed in pointing to `NULL`. + * Furthermore, if the input `referenced_types` sequence has types with identical names but + * differing structures, this function will return `RCUTILS_RET_INVALID_ARGUMENT` and fail. + * + * Ownership: + * - The caller assumes ownership of the output `rcutils_hash_map_t` and must free it and its + * elements, but NOT finalize its values. + * - The output `rcutils_hash_map_t` has values that borrows the `referenced_types` arg's + * `IndividualTypeDescription` elements. It is not authorized to deallocate them and it cannot + * outlive the `referenced_types` it borrows from. + * - Finalizing the output `rcutils_hash_map_t` should not result in its values getting finalized. + * + *
+ * Attribute | Adherence + * ------------------ | ------------- + * Allocates Memory | Yes + * Thread-Safe | No + * Uses Atomics | No + * Lock-Free | Yes + * + * \param[in] referenced_types the referenced individual type descriptions to get the referenced + * types from + * \param[in] allocator the allocator to use through out the lifetime of the hash_map + * \param[out] hash_map `rcutils_hash_map_t` to be initialized, containing + * `IndividualTypeDescription` objects keyed by their type names. + * Must point to `NULL`, outputs pointing to `NULL` if error. + * \return #RCUTILS_RET_OK if successful, or + * \return #RCUTILS_RET_BAD_ALLOC if memory allocation fails, or + * \return #RCUTILS_RET_INVALID_ARGUMENT for invalid arguments, or + * \return #RCUTILS_RET_ERROR if an unknown error occurs. + */ +ROSIDL_GENERATOR_C_PUBLIC +rcutils_ret_t +rosidl_runtime_c_type_description_utils_get_referenced_type_description_map( + const rosidl_runtime_c__type_description__IndividualTypeDescription__Sequence * referenced_types, + const rcutils_allocator_t * allocator, + rcutils_hash_map_t ** hash_map); + + +// ================================================================================================= +// DESCRIPTION VALIDITY +// ================================================================================================= + +/// Return a map of only the referenced type descriptions that are recursively necessary. +/** + * The `seen_map` output arg must be passed in pointing to `NULL`. + * It's a parameter so it can be passed into subsequent recursive calls to traverse nested types. + * + * A referenced type description is recursively necessary if it is either: + * - Needed by a field of the main IndividualTypeDescription + * - Needed by a field of any prior necessary referenced type descriptions (hence recursive) + * + * For more clarity, an unnecessary referenced type description will not be + * referenced when parsing a TypeDescription, and hence can be excluded. + * + * Ownership: + * - The caller assumes ownership of the output `rcutils_hash_map_t` and must free it and its + * elements, but NOT finalize its values. + * - The output `rcutils_hash_map_t` has values that borrows the `referenced_types_map` arg's + * `IndividualTypeDescription` values. It is not authorized to deallocate them and it cannot + * outlive owner it borrows from. + * - Finalizing the output `rcutils_hash_map_t` should not result in its values getting finalized. + * + * Procedure: + * 1. Create seen map + * 2. [Iterate through fields]: + * 3. If field is not nested type or field's nested type is seen in the seen map: + * - Continue + * 4. If nested type is missing in referenced types or nested type name is empty: + * - Throw error + * 5. Else: + * - Add to seen map + * - Recurse on referenced type + * 6. Output seen map + * + *
+ * Attribute | Adherence + * ------------------ | ------------- + * Allocates Memory | Yes + * Thread-Safe | No + * Uses Atomics | No + * Lock-Free | Yes + * + * \param[in] main_type_description the main individual type description to check the fields of + * \param[in] referenced_types_map a map of referenced `IndividualTypeDescription` objects from the + * main individual type description's parent `TypeDescription` object, keyed by their type names. + * \param[in] allocator the allocator to use through out the lifetime of the hash_map. + * \param[in,out] seen_map `rcutils_hash_map_t` of seen necessary `IndividualTypeDescription` + * objects keyed by their type names. Used in recursive calls. Must point to + * `NULL` for user's top level call, outputs pointing to `NULL` if error. + * \return #RCUTILS_RET_OK if successful, or + * \return #RCUTILS_RET_INVALID_ARGUMENT for invalid arguments, or + * \return #RCUTILS_RET_BAD_ALLOC if memory allocation fails, or + * \return #RCUTILS_RET_NOT_FOUND if passed referenced types are missing necessary types, or + * \return #RCUTILS_RET_ERROR if an unknown error occurs. + */ +ROSIDL_GENERATOR_C_PUBLIC +rcutils_ret_t +rosidl_runtime_c_type_description_utils_get_necessary_referenced_type_descriptions_map( + const rosidl_runtime_c__type_description__IndividualTypeDescription * main_type_description, + const rcutils_hash_map_t * referenced_types_map, + const rcutils_allocator_t * allocator, + rcutils_hash_map_t ** seen_map); + +/// Deep copy a map of individual type descriptions into a new IndividualTypeDescription__Sequence. +/** + * The `sequence` output arg must be passed in pointing to `NULL`. + * + * This method also validates that each IndividualTypeDescription in the map has a type name that + * matches the key it was indexed by. + * + * Ownership: + * - The caller assumes ownership of the output `IndividualTypeDescription__Sequence` and must free + * it. + * - The output `IndividualTypeDescription__Sequence` deep copies the values from the `hash_map` arg + * and so has a separate lifetime from the `hash_map`. + * + *
+ * Attribute | Adherence + * ------------------ | ------------- + * Allocates Memory | Yes + * Thread-Safe | No + * Uses Atomics | No + * Lock-Free | Yes + * + * \param[in] hash_map the referenced type descriptions map to convert (via deep copy) to a sequence + * \param[out] sequence the `IndividualTypeDescription__Sequence` containing copies of the + * `IndividualTypeDescription` objects stored in the values of the input `hash_map` arg. + * Must point to `NULL`, outputs pointing to `NULL` if error. + * \param[in] sort sorts the referenced type descriptions if true, best effort + * \return #RCUTILS_RET_OK if successful, or + * \return #RCUTILS_RET_INVALID_ARGUMENT for invalid arguments, or + * \return #RCUTILS_RET_BAD_ALLOC if memory allocation fails, or + * \return #RCUTILS_RET_ERROR if an unknown error occurs. + */ +ROSIDL_GENERATOR_C_PUBLIC +rcutils_ret_t +rosidl_runtime_c_type_description_utils_copy_init_sequence_from_referenced_type_descriptions_map( + const rcutils_hash_map_t * hash_map, + rosidl_runtime_c__type_description__IndividualTypeDescription__Sequence ** sequence, + bool sort); + +/// Helper comparison function for the sorting function +ROSIDL_GENERATOR_C_PUBLIC +int +rosidl_runtime_c_type_description_utils_referenced_type_description_sequence_sort_compare( + const void * lhs, const void * rhs); + +ROSIDL_GENERATOR_C_PUBLIC +rcutils_ret_t +rosidl_runtime_c_type_description_utils_sort_referenced_type_descriptions_in_place( + rosidl_runtime_c__type_description__IndividualTypeDescription__Sequence * sequence); + +/// Remove unnecessary referenced type descriptions from a sequence of referenced types. +/** + * IndividualTypeDescription elements are COPY ASSIGNED in-place, and the original sequence is + * shrunken afterwards. + * + * DOES NOT SORT AFTER PRUNING! Call sort separately. + */ +ROSIDL_GENERATOR_C_PUBLIC +rcutils_ret_t +rosidl_runtime_c_type_description_utils_prune_referenced_type_descriptions_in_place( + const rosidl_runtime_c__type_description__IndividualTypeDescription * main_type_description, + rosidl_runtime_c__type_description__IndividualTypeDescription__Sequence * referenced_types); + +/// Check if field is valid +/** + * A field is valid if: + * - Its name is not empty + * - Note this does not check for valid chars/double underscores + * - Its field type: + * - Is set + * - Has a non-empty nested type name if nested + */ +ROSIDL_GENERATOR_C_PUBLIC +rcutils_ret_t +rosidl_runtime_c_type_description_utils_field_is_valid( + const rosidl_runtime_c__type_description__Field * field); + +/// Check if individual type description is valid +/** + * An individual type description is valid if: + * - Its type name is not empty + * - Note this does not check for valid chars etc. + * - All of its fields are valid + * - It does not have duplicate fields + */ +ROSIDL_GENERATOR_C_PUBLIC +rcutils_ret_t +rosidl_runtime_c_type_description_utils_individual_type_description_is_valid( + const rosidl_runtime_c__type_description__IndividualTypeDescription * description); + +/// Check if type description is valid +/** + * An type description is valid if: + * - Its main individual type description is valid + * - Its referenced type descriptions are: + * - Not duplicated + * - Not missing necessary type descriptions + * - Have no unnecessary type descriptions + * - Individually valid + * - Sorted + */ +ROSIDL_GENERATOR_C_PUBLIC +rcutils_ret_t +rosidl_runtime_c_type_description_utils_type_description_is_valid( + const rosidl_runtime_c__type_description__TypeDescription * description); + +/// This is on a best effort basis, it won't work if the fields of the main or necessary referenced +/// types are invalid. It prunes then sorts. +ROSIDL_GENERATOR_C_PUBLIC +rcutils_ret_t +rosidl_runtime_c_type_description_utils_coerce_to_valid_type_description_in_place( + rosidl_runtime_c__type_description__TypeDescription * type_description); + + +// ================================================================================================= +// DESCRIPTION CONSTRUCTION +// ================================================================================================= + +/// Construct a new populated `Field` object. +/** + * NOTE: The `field` output arg must be passed in pointing to `NULL`. + * + * Ownership: + * - The caller assumes ownership of the output `Field` and must free it and its members. + * - The char * members are copied. + * + *
+ * Attribute | Adherence + * ------------------ | ------------- + * Allocates Memory | Yes + * Thread-Safe | No + * Uses Atomics | No + * Lock-Free | Yes + * + * \param[in] name the name of the field + * \param[in] name_length the string length of `name` (not counting the null terminator) + * \param[in] type_id the type id of the field (follows the type_description_interfaces definition) + * \param[in] capacity capacity of the field (if it is an array or sequence) + * \param[in] string_capacity capacity of string elements (if the field is an array or sequence of + * strings) + * \param[in] nested_type_name the nested type name of the field (if it is a nested type) + * \param[in] nested_type_name_length the string length of `nested_type_name` (not counting the null + * terminator) + * \param[in] default_value literal default value of the field as a string, as it appeared in the + * original message definition + * \param[in] default_value_length the string length of `default_value` (not counting the null + * terminator) + * \param[out] field `Field` to be initialized. Must point to `NULL`, outputs pointing to `NULL` if + * error. + * \return #RCUTILS_RET_OK if successful, or + * \return #RCUTILS_RET_BAD_ALLOC if memory allocation fails, or + * \return #RCUTILS_RET_INVALID_ARGUMENT for invalid arguments, or + * \return #RCUTILS_RET_ERROR if an unknown error occurs. + */ +ROSIDL_GENERATOR_C_PUBLIC +rcutils_ret_t +rosidl_runtime_c_type_description_utils_create_field( + const char * name, size_t name_length, + uint8_t type_id, uint64_t capacity, uint64_t string_capacity, + const char * nested_type_name, size_t nested_type_name_length, + const char * default_value, size_t default_value_length, + rosidl_runtime_c__type_description__Field ** field); + +/// Construct a new populated `IndividualTypeDescription` object, with fields sequence of size 0. +/** + * NOTE: The `individual_description` output arg must be passed in pointing to `NULL`. + * + * Ownership: + * - The caller assumes ownership of the output `IndividualTypeDescription` and must free it and its + * members. + * - The char * members are copied. + * + *
+ * Attribute | Adherence + * ------------------ | ------------- + * Allocates Memory | Yes + * Thread-Safe | No + * Uses Atomics | No + * Lock-Free | Yes + * + * \param[in] type_name the name of the individual type description + * \param[in] type_name_length the string length of `type_name` (not counting the null terminator) + * \param[out] individual_description `IndividualTypeDescription` to be initialized. Must point to + * `NULL`, outputs pointing to `NULL` if error. + * \return #RCUTILS_RET_OK if successful, or + * \return #RCUTILS_RET_BAD_ALLOC if memory allocation fails, or + * \return #RCUTILS_RET_INVALID_ARGUMENT for invalid arguments, or + * \return #RCUTILS_RET_ERROR if an unknown error occurs. + */ +ROSIDL_GENERATOR_C_PUBLIC +rcutils_ret_t +rosidl_runtime_c_type_description_utils_create_individual_type_description( + const char * type_name, size_t type_name_length, + rosidl_runtime_c__type_description__IndividualTypeDescription ** individual_description); + +/// Construct a new populated `TypeDescription` object, with fields and referenced types sequences +/// of size 0. +/** + * NOTE: The `description` output arg must be passed in pointing to `NULL`. + * + * Ownership: + * - The caller assumes ownership of the output `TypeDescription` and must free it and its + * members. + * - The char * members are copied. + * + *
+ * Attribute | Adherence + * ------------------ | ------------- + * Allocates Memory | Yes + * Thread-Safe | No + * Uses Atomics | No + * Lock-Free | Yes + * + * \param[in] type_name the name of the type description's main individual type + * \param[in] type_name_length the string length of `type_name` (not counting the null terminator) + * \param[out] type_description `TypeDescription` to be initialized. Must point to `NULL`, + * outputs pointing to `NULL` if error. + * \return #RCUTILS_RET_OK if successful, or + * \return #RCUTILS_RET_BAD_ALLOC if memory allocation fails, or + * \return #RCUTILS_RET_INVALID_ARGUMENT for invalid arguments, or + * \return #RCUTILS_RET_ERROR if an unknown error occurs. + */ +ROSIDL_GENERATOR_C_PUBLIC +rcutils_ret_t +rosidl_runtime_c_type_description_utils_create_type_description( + const char * type_name, size_t type_name_length, + rosidl_runtime_c__type_description__TypeDescription ** type_description); + +/// Append a `Field` to an `IndividualTypeDescription`, extending the sequence. +/** + * + * Ownership: + * - A deep-copy of the `field` is appended to `individual_type_description` on success, the + * `individual_type_description` is then responsible for freeing the copied `field`. + * - The ownership of the input `field` is not transferred, it must still be freed. + * + *
+ * Attribute | Adherence + * ------------------ | ------------- + * Allocates Memory | Yes + * Thread-Safe | No + * Uses Atomics | No + * Lock-Free | Yes + * + * \param[in,out] individual_type_description the individual type description to append to, noop on + * failure + * \param[in,out] field the field to append + * \return #RCUTILS_RET_OK if successful, or + * \return #RCUTILS_RET_BAD_ALLOC if memory allocation fails, or + * \return #RCUTILS_RET_INVALID_ARGUMENT for invalid arguments, or + * \return #RCUTILS_RET_ERROR if an unknown error occurs. + */ +ROSIDL_GENERATOR_C_PUBLIC +rcutils_ret_t +rosidl_runtime_c_type_description_utils_append_field( + rosidl_runtime_c__type_description__IndividualTypeDescription * individual_type_description, + rosidl_runtime_c__type_description__Field * field); + +/// Append a referenced `IndividualTypeDescription` to a `TypeDescription`, extending it. +/** + * + * Ownership: + * - A deep-copy of the `referenced_type_description` is appended to `type_description` on success, + * the `type_description` is then responsible for freeing the copied + * `referenced_type_description`. + * - The ownership of the input `referenced_type_description` is not transferred, it must still be + * freed. + * + *
+ * Attribute | Adherence + * ------------------ | ------------- + * Allocates Memory | Yes + * Thread-Safe | No + * Uses Atomics | No + * Lock-Free | Yes + * + * \param[in,out] type_description the type description to append to, noop on failure + * \param[in,out] individual_type_description the individual type description to append + * \param[in] sort sorts the referenced type descriptions if true, best effort + * \return #RCUTILS_RET_OK if successful, or + * \return #RCUTILS_RET_BAD_ALLOC if memory allocation fails, or + * \return #RCUTILS_RET_INVALID_ARGUMENT for invalid arguments, or + * \return #RCUTILS_RET_ERROR if an unknown error occurs. + */ +ROSIDL_GENERATOR_C_PUBLIC +rcutils_ret_t +rosidl_runtime_c_type_description_utils_append_referenced_individual_type_description( + rosidl_runtime_c__type_description__TypeDescription * type_description, + rosidl_runtime_c__type_description__IndividualTypeDescription * referenced_type_description, + bool sort); + +/// Append a referenced `TypeDescription` to a `TypeDescription`, extending it with recursive types. +/** + * + * Ownership: + * - A deep-copy of the `type_description_to_append` object's main individual type description is + * appended to `type_description` on success, the `type_description` is then responsible for + * freeing the copied individual type descriptions. + * - Deep-copies of the `type_description_to_append` object's referenced individual type + * descriptions are appended to `type_description` on success, the `type_description` is then + * responsible for freeing the copied individual type descriptions (or the remaining ones if + * they are pruned). + * - The ownership of the input `type_description` is not transferred, it must still be + * freed. + * + *
+ * Attribute | Adherence + * ------------------ | ------------- + * Allocates Memory | Yes + * Thread-Safe | No + * Uses Atomics | No + * Lock-Free | Yes + * + * \param[in,out] type_description the type description to append to, noop on failure + * \param[in,out] type_description the type description to append + * \param[in] coerce_to_valid coerces input type_description to valid before output if true + * (pruning and sorting), best effort + * \return #RCUTILS_RET_OK if successful, or + * \return #RCUTILS_RET_BAD_ALLOC if memory allocation fails, or + * \return #RCUTILS_RET_INVALID_ARGUMENT for invalid arguments, or + * \return #RCUTILS_RET_ERROR if an unknown error occurs. + */ +// This adds the type description's main description as a referenced type, and all necessary +// referenced types. Then it prunes and sorts the resulting referenced types sequence. +ROSIDL_GENERATOR_C_PUBLIC +rcutils_ret_t +rosidl_runtime_c_type_description_utils_append_referenced_type_description( + rosidl_runtime_c__type_description__TypeDescription * type_description, + rosidl_runtime_c__type_description__TypeDescription * type_description_to_append, + bool coerce_to_valid); + +// Create a type description from a referenced description, then validate if coerce_to_valid is true +// This is done by copy!! This allocates memory and the caller is responsible for deallocating the +// output +ROSIDL_GENERATOR_C_PUBLIC +rcutils_ret_t +rosidl_runtime_c_type_description_utils_get_referenced_type_description_as_type_description( + const rosidl_runtime_c__type_description__IndividualTypeDescription__Sequence * + referenced_descriptions, + const rosidl_runtime_c__type_description__IndividualTypeDescription * referenced_description, + rosidl_runtime_c__type_description__TypeDescription ** output_description, + bool coerce_to_valid); + +// Create a type description from a referenced description, then validate if coerce_to_valid is true +// This is done by copy!! This allocates memory and the caller is responsible for deallocating the +// output +ROSIDL_GENERATOR_C_PUBLIC +rcutils_ret_t +rosidl_runtime_c_type_description_utils_get_referenced_type_description_as_type_description( + const rosidl_runtime_c__type_description__IndividualTypeDescription__Sequence * + referenced_descriptions, + const rosidl_runtime_c__type_description__IndividualTypeDescription * referenced_description, + rosidl_runtime_c__type_description__TypeDescription ** output_description, + bool coerce_to_valid); + +/// In-place replace substrings in an individual description's type name and nested names in fields +/** + * This means the `IndividualTypeDescription`'s' type_name will get replaced and all references + * to any nested_type_names in the description's fields. + * + * NOTE(methylDragon): This method is pretty inefficient because it doesn't do checking and will + * re-copy all names. + */ +ROSIDL_GENERATOR_C_PUBLIC +rcutils_ret_t +rosidl_runtime_c_type_description_utils_repl_individual_type_description_type_names_in_place( + rosidl_runtime_c__type_description__IndividualTypeDescription * individual_type_description, + const char * from, + const char * to); + +/// In-place replace substrings across all type names (and references to those names) +/** + * This means all `IndividualTypeDescription` type_names will get replaced, in the main description + * and referenced type descriptions, and also all references to those names (nested_type_name.) + * + * NOTE(methylDragon): This method is pretty inefficient because it doesn't do checking and will + * re-copy all names. + */ +ROSIDL_GENERATOR_C_PUBLIC +rcutils_ret_t +rosidl_runtime_c_type_description_utils_repl_all_type_description_type_names_in_place( + rosidl_runtime_c__type_description__TypeDescription * type_description, + const char * from, + const char * to); + +#ifdef __cplusplus +} +#endif + +#endif // ROSIDL_RUNTIME_C__TYPE_DESCRIPTION_UTILS_H_ diff --git a/rosidl_runtime_c/src/type_description_utils.c b/rosidl_runtime_c/src/type_description_utils.c new file mode 100644 index 000000000..4e620f119 --- /dev/null +++ b/rosidl_runtime_c/src/type_description_utils.c @@ -0,0 +1,1448 @@ +// 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 "rosidl_runtime_c/type_description_utils.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "rosidl_runtime_c/type_description/field__functions.h" +#include "rosidl_runtime_c/type_description/field__struct.h" +#include "rosidl_runtime_c/type_description/individual_type_description__functions.h" +#include "rosidl_runtime_c/type_description/individual_type_description__struct.h" +#include "rosidl_runtime_c/type_description/type_description__functions.h" +#include "rosidl_runtime_c/type_description/type_description__struct.h" + +// Modified from http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 +size_t next_power_of_two(size_t v) +{ + size_t shf = 0; + v--; + for (size_t i = 0; shf < sizeof(size_t) * 4; ++i) { + shf = (((size_t) 1) << i); + v |= v >> shf; + } + v++; + return v > 1 ? v : 1; +} + +// ================================================================================================= +// GET BY NAME +// ================================================================================================= +rcutils_ret_t +rosidl_runtime_c_type_description_utils_find_field( + const rosidl_runtime_c__type_description__Field__Sequence * fields, + const char * name, + rosidl_runtime_c__type_description__Field ** field) +{ + RCUTILS_CHECK_ARGUMENT_FOR_NULL(fields, RCUTILS_RET_INVALID_ARGUMENT); + RCUTILS_CHECK_ARGUMENT_FOR_NULL(name, RCUTILS_RET_INVALID_ARGUMENT); + RCUTILS_CHECK_ARGUMENT_FOR_NULL(field, RCUTILS_RET_INVALID_ARGUMENT); + if (*field != NULL) { + RCUTILS_SET_ERROR_MSG("'field' output argument is not pointing to null"); + return RCUTILS_RET_INVALID_ARGUMENT; + } + + for (size_t i = 0; i < fields->size; ++i) { + if (strcmp(fields->data[i].name.data, name) == 0) { + *field = &fields->data[i]; + return RCUTILS_RET_OK; + } + } + + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING("Could not find field: %s", name); + return RCUTILS_RET_NOT_FOUND; +} + +rcutils_ret_t +rosidl_runtime_c_type_description_utils_find_referenced_type_description( + const rosidl_runtime_c__type_description__IndividualTypeDescription__Sequence * referenced_types, + const char * type_name, + rosidl_runtime_c__type_description__IndividualTypeDescription ** referenced_type) +{ + RCUTILS_CHECK_ARGUMENT_FOR_NULL(referenced_types, RCUTILS_RET_INVALID_ARGUMENT); + RCUTILS_CHECK_ARGUMENT_FOR_NULL(type_name, RCUTILS_RET_INVALID_ARGUMENT); + RCUTILS_CHECK_ARGUMENT_FOR_NULL(referenced_type, RCUTILS_RET_INVALID_ARGUMENT); + if (*referenced_type != NULL) { + RCUTILS_SET_ERROR_MSG("'referenced_type' output argument is not pointing to null"); + return RCUTILS_RET_INVALID_ARGUMENT; + } + + for (size_t i = 0; i < referenced_types->size; ++i) { + if (strcmp(referenced_types->data[i].type_name.data, type_name) == 0) { + *referenced_type = &referenced_types->data[i]; + return RCUTILS_RET_OK; + } + } + + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Could not find referenced type description: %s", type_name); + return RCUTILS_RET_NOT_FOUND; +} + + +// ================================================================================================= +// HASH MAPS +// ================================================================================================= +rcutils_ret_t +rosidl_runtime_c_type_description_utils_get_field_map( + const rosidl_runtime_c__type_description__IndividualTypeDescription * individual_description, + const rcutils_allocator_t * allocator, + rcutils_hash_map_t ** hash_map) +{ + RCUTILS_CHECK_ARGUMENT_FOR_NULL(individual_description, RCUTILS_RET_INVALID_ARGUMENT); + RCUTILS_CHECK_ARGUMENT_FOR_NULL(allocator, RCUTILS_RET_INVALID_ARGUMENT); + RCUTILS_CHECK_ARGUMENT_FOR_NULL(hash_map, RCUTILS_RET_INVALID_ARGUMENT); + if (*hash_map != NULL) { + RCUTILS_SET_ERROR_MSG("'hash_map' output argument is not pointing to null"); + return RCUTILS_RET_INVALID_ARGUMENT; + } + + rcutils_hash_map_t * out = allocator->allocate(sizeof(rcutils_hash_map_t), allocator->state); + if (out == NULL) { + RCUTILS_SET_ERROR_MSG("Could not allocate output hash map"); + return RCUTILS_RET_BAD_ALLOC; + } + *out = rcutils_get_zero_initialized_hash_map(); + + rcutils_ret_t ret = RCUTILS_RET_ERROR; + + ret = rcutils_hash_map_init( + out, next_power_of_two(individual_description->fields.size), + sizeof(char *), sizeof(rosidl_runtime_c__type_description__Field *), + rcutils_hash_map_string_hash_func, rcutils_hash_map_string_cmp_func, + allocator); + if (ret != RCUTILS_RET_OK) { + allocator->deallocate(out, allocator->state); + rcutils_error_string_t error_string = rcutils_get_error_string(); + rcutils_reset_error(); + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING("Could not init hash map:\n%s", error_string.str); + return ret; + } + + for (size_t i = 0; i < individual_description->fields.size; ++i) { + rosidl_runtime_c__type_description__Field * tmp = &individual_description->fields.data[i]; + // Passing tmp is fine even if tmp goes out of scope later since it copies in the set method... + ret = rcutils_hash_map_set(out, &individual_description->fields.data[i].name.data, &tmp); + if (ret != RCUTILS_RET_OK) { + rcutils_error_string_t error_string = rcutils_get_error_string(); + rcutils_reset_error(); + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Could not set hash map entry for field: %s:\n%s", + individual_description->fields.data[i].name.data, + error_string.str); + goto fail; + } + } + + *hash_map = out; + return RCUTILS_RET_OK; + +fail: + { + if (rcutils_hash_map_fini(out) != RCUTILS_RET_OK) { + RCUTILS_SAFE_FWRITE_TO_STDERR("While handling another error, failed to finalize hash map"); + } + allocator->deallocate(out, allocator->state); + } + return ret; +} + +rcutils_ret_t +rosidl_runtime_c_type_description_utils_get_referenced_type_description_map( + const rosidl_runtime_c__type_description__IndividualTypeDescription__Sequence * referenced_types, + const rcutils_allocator_t * allocator, + rcutils_hash_map_t ** hash_map) +{ + RCUTILS_CHECK_ARGUMENT_FOR_NULL(referenced_types, RCUTILS_RET_INVALID_ARGUMENT); + RCUTILS_CHECK_ARGUMENT_FOR_NULL(allocator, RCUTILS_RET_INVALID_ARGUMENT); + RCUTILS_CHECK_ARGUMENT_FOR_NULL(hash_map, RCUTILS_RET_INVALID_ARGUMENT); + if (*hash_map != NULL) { + RCUTILS_SET_ERROR_MSG("'hash_map' output argument is not pointing to null"); + return RCUTILS_RET_INVALID_ARGUMENT; + } + + rcutils_hash_map_t * out = allocator->allocate(sizeof(rcutils_hash_map_t), allocator->state); + if (out == NULL) { + RCUTILS_SET_ERROR_MSG("Could not allocate output hash map"); + return RCUTILS_RET_BAD_ALLOC; + } + *out = rcutils_get_zero_initialized_hash_map(); + + rcutils_ret_t ret = RCUTILS_RET_ERROR; + rcutils_ret_t fail_ret = RCUTILS_RET_ERROR; + + ret = rcutils_hash_map_init( + out, next_power_of_two(referenced_types->size), + sizeof(char *), sizeof(rosidl_runtime_c__type_description__IndividualTypeDescription *), + rcutils_hash_map_string_hash_func, rcutils_hash_map_string_cmp_func, + allocator); + if (ret != RCUTILS_RET_OK) { + allocator->deallocate(out, allocator->state); + RCUTILS_SET_ERROR_MSG("Could not init hash map"); + return ret; + } + + for (size_t i = 0; i < referenced_types->size; ++i) { + rosidl_runtime_c__type_description__IndividualTypeDescription * tmp = + &referenced_types->data[i]; + + // Check for duplicate referenced types + if (rcutils_hash_map_key_exists(out, &referenced_types->data[i].type_name.data)) { + rosidl_runtime_c__type_description__IndividualTypeDescription * stored_description; + ret = rcutils_hash_map_get( + out, &referenced_types->data[i].type_name.data, &stored_description); + if (ret != RCUTILS_RET_OK) { + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Could not get stored description: %s", referenced_types->data[i].type_name.data); + fail_ret = ret; // Most likely a RCUTILS_RET_NOT_FOUND + goto fail; + } + + if (!rosidl_runtime_c__type_description__IndividualTypeDescription__are_equal( + &referenced_types->data[i], stored_description)) + { + // Non-identical duplicate referenced types is invalid (it's ambiguous which one to use) + RCUTILS_SET_ERROR_MSG( + "Passed referenced IndividualTypeDescriptions has non-identical duplicate types"); + fail_ret = RCUTILS_RET_INVALID_ARGUMENT; + goto fail; + } + } + + // Passing tmp is fine even if tmp goes out of scope later since it copies in the set method... + ret = rcutils_hash_map_set(out, &referenced_types->data[i].type_name.data, &tmp); + if (ret != RCUTILS_RET_OK) { + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Could not set hash map entry for referenced type: %s", + referenced_types->data[i].type_name.data); + fail_ret = ret; + goto fail; + } + } + + size_t map_length; + ret = rcutils_hash_map_get_size(out, &map_length); + if (ret != RCUTILS_RET_OK) { + RCUTILS_SET_ERROR_MSG("Could not get size of hash map for validation"); + fail_ret = RCUTILS_RET_ERROR; + goto fail; + } + + *hash_map = out; + return RCUTILS_RET_OK; + +fail: + { + rcutils_ret_t fini_ret = rcutils_hash_map_fini(out); + if (fini_ret != RCUTILS_RET_OK) { + RCUTILS_SAFE_FWRITE_TO_STDERR("While handling another error, failed to finalize hash map"); + } + allocator->deallocate(out, allocator->state); + } + return fail_ret; +} + + +// ================================================================================================= +// DESCRIPTION VALIDITY +// ================================================================================================= +rcutils_ret_t +rosidl_runtime_c_type_description_utils_get_necessary_referenced_type_descriptions_map( + const rosidl_runtime_c__type_description__IndividualTypeDescription * main_type_description, + const rcutils_hash_map_t * referenced_types_map, + const rcutils_allocator_t * allocator, + rcutils_hash_map_t ** seen_map) +{ + RCUTILS_CHECK_ARGUMENT_FOR_NULL(main_type_description, RCUTILS_RET_INVALID_ARGUMENT); + HASH_MAP_VALIDATE_HASH_MAP(referenced_types_map); + RCUTILS_CHECK_ARGUMENT_FOR_NULL(allocator, RCUTILS_RET_INVALID_ARGUMENT); + RCUTILS_CHECK_ARGUMENT_FOR_NULL(seen_map, RCUTILS_RET_INVALID_ARGUMENT); + + // Only true for the top level call, so we can determine when to finalize the map + bool top_level_call = false; + rcutils_ret_t ret = RCUTILS_RET_ERROR; + rcutils_ret_t fail_ret = RCUTILS_RET_ERROR; + + // 1. Init new hash map only on the top level call + if (!*seen_map) { + top_level_call = true; + + *seen_map = allocator->allocate(sizeof(rcutils_hash_map_t), allocator->state); + if (*seen_map == NULL) { + RCUTILS_SET_ERROR_MSG("Could not allocate hash map"); + return RCUTILS_RET_BAD_ALLOC; + } + **seen_map = rcutils_get_zero_initialized_hash_map(); + + size_t referenced_types_map_size; + ret = rcutils_hash_map_get_size(referenced_types_map, &referenced_types_map_size); + if (ret != RCUTILS_RET_OK) { + allocator->deallocate(*seen_map, allocator->state); + RCUTILS_SET_ERROR_MSG("Could not get size of referenced types hash map"); + *seen_map = NULL; + return RCUTILS_RET_ERROR; + } + + ret = rcutils_hash_map_init( + *seen_map, next_power_of_two(referenced_types_map_size), + sizeof(char *), sizeof(rosidl_runtime_c__type_description__IndividualTypeDescription *), + rcutils_hash_map_string_hash_func, rcutils_hash_map_string_cmp_func, + allocator); + if (ret != RCUTILS_RET_OK) { + allocator->deallocate(*seen_map, allocator->state); + RCUTILS_SET_ERROR_MSG("Could not init hash map"); + *seen_map = NULL; + return RCUTILS_RET_BAD_ALLOC; + } + } + + // 2. Iterate through fields + for (size_t i = 0; i < main_type_description->fields.size; ++i) { + rosidl_runtime_c__type_description__Field * field = &main_type_description->fields.data[i]; + // 3. Skip cases + // continue if field is not nested type or nested type is in seen map: + if ((field->type.type_id % + ROSIDL_RUNTIME_C_TYPE_DESCRIPTION_UTILS_SEQUENCE_TYPE_ID_MASK) != 1 || + rcutils_hash_map_key_exists(*seen_map, &field->type.nested_type_name.data)) + { + continue; + } + + // 4. Error cases + // Referenced type does not exist + if (!rcutils_hash_map_key_exists(referenced_types_map, &field->type.nested_type_name.data)) { + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Missing referenced type: %s", field->type.nested_type_name.data); + fail_ret = RCUTILS_RET_NOT_FOUND; + goto fail; + } + // Nested name empty + if (field->type.nested_type_name.size == 0) { + RCUTILS_SET_ERROR_MSG("Missing referenced type name"); + fail_ret = RCUTILS_RET_INVALID_ARGUMENT; + goto fail; + } + + // 5. Add to seen map (we didn't skip and didn't error out) + rosidl_runtime_c__type_description__IndividualTypeDescription * necessary_description; + + ret = rcutils_hash_map_get( + referenced_types_map, &field->type.nested_type_name.data, &necessary_description); + if (ret != RCUTILS_RET_OK) { + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Could not get necessary referenced type: %s", field->type.nested_type_name.data); + fail_ret = ret; // Most likely a RCUTILS_RET_NOT_FOUND + goto fail; + } + + // Check for mismatched name + if (strcmp(field->type.nested_type_name.data, necessary_description->type_name.data) != 0) { + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Necessary referenced type name (%s) did not match field's nested type name (%s)", + necessary_description->type_name.data, + field->type.nested_type_name.data); + fail_ret = RCUTILS_RET_INVALID_ARGUMENT; + goto fail; + } + + // Add to map (finally!!) + ret = rcutils_hash_map_set( + *seen_map, &field->type.nested_type_name.data, &necessary_description); + if (ret != RCUTILS_RET_OK) { + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Failed to set hash map for key: %s", field->type.nested_type_name.data); + fail_ret = ret; + goto fail; + } + + // Recurse on fields on necessary_description + ret = rosidl_runtime_c_type_description_utils_get_necessary_referenced_type_descriptions_map( + necessary_description, referenced_types_map, allocator, seen_map); + if (ret != RCUTILS_RET_OK) { + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Recursion failed on: %s", necessary_description->type_name.data); + fail_ret = ret; + goto fail; + } + } // End field iteration + + return RCUTILS_RET_OK; + +fail: + if (top_level_call) { + rcutils_ret_t fini_ret = rcutils_hash_map_fini(*seen_map); + if (fini_ret != RCUTILS_RET_OK) { + RCUTILS_SAFE_FWRITE_TO_STDERR("While handling another error, failed to finalize hash map"); + } + allocator->deallocate(*seen_map, allocator->state); + *seen_map = NULL; + } + return fail_ret; +} + +rcutils_ret_t +rosidl_runtime_c_type_description_utils_copy_init_sequence_from_referenced_type_descriptions_map( + const rcutils_hash_map_t * hash_map, + rosidl_runtime_c__type_description__IndividualTypeDescription__Sequence ** sequence, + bool sort) +{ + RCUTILS_CHECK_ARGUMENT_FOR_NULL(hash_map, RCUTILS_RET_INVALID_ARGUMENT); + if (*sequence != NULL) { + RCUTILS_SET_ERROR_MSG("'sequence' output argument is not pointing to null"); + return RCUTILS_RET_INVALID_ARGUMENT; + } + + size_t map_length; + rcutils_ret_t ret = RCUTILS_RET_ERROR; + ret = rcutils_hash_map_get_size(hash_map, &map_length); + if (ret != RCUTILS_RET_OK) { + RCUTILS_SET_ERROR_MSG("Could not get size of hash map"); + return RCUTILS_RET_ERROR; + } + *sequence = rosidl_runtime_c__type_description__IndividualTypeDescription__Sequence__create( + map_length); + if (*sequence == NULL) { + RCUTILS_SET_ERROR_MSG("Could allocate sequence"); + return RCUTILS_RET_BAD_ALLOC; + } + + size_t i = 0; + char * key; + rosidl_runtime_c__type_description__IndividualTypeDescription * data; + rcutils_ret_t status = rcutils_hash_map_get_next_key_and_data(hash_map, NULL, &key, &data); + while (RCUTILS_RET_OK == status) { + if (strcmp(key, data->type_name.data) != 0) { + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Necessary referenced type name (%s) did not match key (%s)", data->type_name.data, key); + rosidl_runtime_c__type_description__IndividualTypeDescription__Sequence__destroy(*sequence); + return RCUTILS_RET_INVALID_ARGUMENT; + } + + // Deep copy + if (!rosidl_runtime_c__type_description__IndividualTypeDescription__copy( + data, &((*sequence)->data[i]))) + { + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING("Could not copy type %s to sequence", key); + rosidl_runtime_c__type_description__IndividualTypeDescription__Sequence__destroy(*sequence); + return RCUTILS_RET_BAD_ALLOC; + } + + i += 1; + status = rcutils_hash_map_get_next_key_and_data(hash_map, &key, &key, &data); + } + + if (sort) { + rcutils_ret_t ret = + rosidl_runtime_c_type_description_utils_sort_referenced_type_descriptions_in_place(*sequence); + if (ret != RCUTILS_RET_OK) { + RCUTILS_SET_ERROR_MSG("Could not sort copy of referenced type descriptions for validation"); + return ret; + } + } + + return RCUTILS_RET_OK; +} + +int +rosidl_runtime_c_type_description_utils_referenced_type_description_sequence_sort_compare( + const void * lhs, const void * rhs) +{ + rosidl_runtime_c__type_description__IndividualTypeDescription * left = + (rosidl_runtime_c__type_description__IndividualTypeDescription *)lhs; + rosidl_runtime_c__type_description__IndividualTypeDescription * right = + (rosidl_runtime_c__type_description__IndividualTypeDescription *)rhs; + if (left == NULL) { + return right == NULL ? 0 : 1; + } else if (right == NULL) { + return -1; + } + return strcmp(left->type_name.data, right->type_name.data); +} + +rcutils_ret_t +rosidl_runtime_c_type_description_utils_sort_referenced_type_descriptions_in_place( + rosidl_runtime_c__type_description__IndividualTypeDescription__Sequence * sequence) +{ + RCUTILS_CHECK_ARGUMENT_FOR_NULL(sequence, RCUTILS_RET_INVALID_ARGUMENT); + return rcutils_qsort( + sequence->data, + sequence->size, + sizeof(rosidl_runtime_c__type_description__IndividualTypeDescription), + rosidl_runtime_c_type_description_utils_referenced_type_description_sequence_sort_compare); +} + +rcutils_ret_t +rosidl_runtime_c_type_description_utils_prune_referenced_type_descriptions_in_place( + const rosidl_runtime_c__type_description__IndividualTypeDescription * main_type_description, + rosidl_runtime_c__type_description__IndividualTypeDescription__Sequence * referenced_types) +{ + RCUTILS_CHECK_ARGUMENT_FOR_NULL(main_type_description, RCUTILS_RET_INVALID_ARGUMENT); + RCUTILS_CHECK_ARGUMENT_FOR_NULL(referenced_types, RCUTILS_RET_INVALID_ARGUMENT); + + rcutils_ret_t ret = RCUTILS_RET_ERROR; + + rcutils_hash_map_t * referenced_types_map = NULL; + rcutils_hash_map_t * necessary_map = NULL; + rcutils_allocator_t allocator = rcutils_get_default_allocator(); + + ret = rosidl_runtime_c_type_description_utils_get_referenced_type_description_map( + referenced_types, &allocator, &referenced_types_map); + if (ret != RCUTILS_RET_OK) { + rcutils_error_string_t error_string = rcutils_get_error_string(); + rcutils_reset_error(); + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Could not construct referenced type description map:\n%s", error_string.str); + return ret; + } + + ret = rosidl_runtime_c_type_description_utils_get_necessary_referenced_type_descriptions_map( + main_type_description, referenced_types_map, &allocator, &necessary_map); + if (ret != RCUTILS_RET_OK) { + rcutils_error_string_t error_string = rcutils_get_error_string(); + rcutils_reset_error(); + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Could not construct necessary referenced type description map:\n%s", error_string.str); + goto end_ref; + } + + size_t map_length; + ret = rcutils_hash_map_get_size(necessary_map, &map_length); + if (ret != RCUTILS_RET_OK) { + rcutils_error_string_t error_string = rcutils_get_error_string(); + rcutils_reset_error(); + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Could not get size of hash map for validation:\n%s", error_string.str); + goto end_necessary; + } + // End early if pruning was not needed + if (referenced_types->size == map_length) { + ret = RCUTILS_RET_OK; + goto end_necessary; + } + + size_t append_count = 0; + char * key; + rosidl_runtime_c__type_description__IndividualTypeDescription * data = NULL; + rcutils_ret_t status = rcutils_hash_map_get_next_key_and_data(necessary_map, NULL, &key, &data); + while (status == RCUTILS_RET_OK) { + if (strcmp(key, data->type_name.data) != 0) { + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Necessary referenced type name (%s) did not match key (%s)", data->type_name.data, key); + ret = RCUTILS_RET_ERROR; + goto end_necessary; + } + + // Deep copy if necessary + if (!rosidl_runtime_c__type_description__IndividualTypeDescription__are_equal( + data, &referenced_types->data[append_count])) + { + if (!rosidl_runtime_c__type_description__IndividualTypeDescription__copy( + data, &referenced_types->data[append_count++])) + { + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Could not copy necessary referenced type description %s to rearrange", key); + ret = RCUTILS_RET_ERROR; + goto end_necessary; + } + } else { + append_count++; + } + status = rcutils_hash_map_get_next_key_and_data(necessary_map, &key, &key, &data); + } + + // Finalize entries after the section of necessary referenced types, and shrink the input sequence + for (size_t i = append_count; i < referenced_types->size; ++i) { + rosidl_runtime_c__type_description__IndividualTypeDescription__fini( + &referenced_types->data[i]); + } + size_t allocation_size = + append_count * sizeof(rosidl_runtime_c__type_description__IndividualTypeDescription); + + rosidl_runtime_c__type_description__IndividualTypeDescription * next_ptr = + allocator.reallocate(referenced_types->data, allocation_size, allocator.state); + if (next_ptr == NULL && allocation_size != 0) { + RCUTILS_SET_ERROR_MSG( + "Could not shrink the necessary referenced type descriptions sequence during rearrangement. " + "Beware: The referenced type descriptions was likely already partially modified in place."); + ret = RCUTILS_RET_BAD_ALLOC; + goto end_necessary; + } + referenced_types->data = next_ptr; + referenced_types->size = append_count; + referenced_types->capacity = append_count; + + ret = RCUTILS_RET_OK; + +end_necessary: + { + if (rcutils_hash_map_fini(necessary_map) != RCUTILS_RET_OK) { + RCUTILS_SAFE_FWRITE_TO_STDERR("While handling another error, failed to finalize hash map"); + } + allocator.deallocate(necessary_map, allocator.state); + } + +end_ref: + { + if (rcutils_hash_map_fini(referenced_types_map) != RCUTILS_RET_OK) { + RCUTILS_SAFE_FWRITE_TO_STDERR("While handling another error, failed to finalize hash map"); + } + allocator.deallocate(referenced_types_map, allocator.state); + } + return ret; +} + +rcutils_ret_t +rosidl_runtime_c_type_description_utils_field_is_valid( + const rosidl_runtime_c__type_description__Field * field) +{ + RCUTILS_CHECK_ARGUMENT_FOR_NULL(field, RCUTILS_RET_INVALID_ARGUMENT); + + if (field->name.size == 0) { + RCUTILS_SET_ERROR_MSG("Field is invalid: Empty name"); + return RCUTILS_RET_NOT_INITIALIZED; + } + if ((field->type.type_id % ROSIDL_RUNTIME_C_TYPE_DESCRIPTION_UTILS_SEQUENCE_TYPE_ID_MASK) == 0) { + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Field `%s` is invalid: Unset type ID", field->name.data); + return RCUTILS_RET_NOT_INITIALIZED; + } + if ((field->type.type_id % ROSIDL_RUNTIME_C_TYPE_DESCRIPTION_UTILS_SEQUENCE_TYPE_ID_MASK) == + 1 && field->type.nested_type_name.size == 0) + { + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Field `%s` is invalid: Field is nested but with empty nested type name", field->name.data); + return RCUTILS_RET_NOT_INITIALIZED; + } + return RCUTILS_RET_OK; +} + +rcutils_ret_t +rosidl_runtime_c_type_description_utils_individual_type_description_is_valid( + const rosidl_runtime_c__type_description__IndividualTypeDescription * description) +{ + RCUTILS_CHECK_ARGUMENT_FOR_NULL(description, RCUTILS_RET_INVALID_ARGUMENT); + + rcutils_ret_t ret = RCUTILS_RET_ERROR; + + if (description->type_name.size == 0) { + RCUTILS_SET_ERROR_MSG("Individual type description is invalid: Empty name"); + return RCUTILS_RET_NOT_INITIALIZED; + } + + for (size_t i = 0; i < description->fields.size; ++i) { + ret = rosidl_runtime_c_type_description_utils_field_is_valid(&description->fields.data[i]); + if (ret != RCUTILS_RET_OK) { + rcutils_error_string_t error_string = rcutils_get_error_string(); + rcutils_reset_error(); + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Individual type description `%s` is invalid: Invalid field:\n%s", + description->type_name.data, + error_string.str); + return ret; + } + } + + rcutils_hash_map_t * hash_map = NULL; + rcutils_allocator_t allocator = rcutils_get_default_allocator(); + + ret = rosidl_runtime_c_type_description_utils_get_field_map( + description, &allocator, &hash_map); + if (ret != RCUTILS_RET_OK) { + rcutils_error_string_t error_string = rcutils_get_error_string(); + rcutils_reset_error(); + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Could not construct field map for validation:\n%s", error_string.str); + return ret; + } + + size_t map_length; + ret = rcutils_hash_map_get_size(hash_map, &map_length); + if (ret != RCUTILS_RET_OK) { + rcutils_error_string_t error_string = rcutils_get_error_string(); + rcutils_reset_error(); + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Could not get size of field map for validation:\n%s", error_string.str); + goto end; + } + + if (description->fields.size != map_length) { + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Individual type description `%s` is invalid: Duplicate fields", description->type_name.data); + ret = RCUTILS_RET_INVALID_ARGUMENT; + goto end; + } + + return RCUTILS_RET_OK; + +end: + { + if (rcutils_hash_map_fini(hash_map) != RCUTILS_RET_OK) { + RCUTILS_SAFE_FWRITE_TO_STDERR("While handling another error, failed to finalize hash map"); + } + allocator.deallocate(hash_map, allocator.state); + return ret; + } +} + +rcutils_ret_t +rosidl_runtime_c_type_description_utils_type_description_is_valid( + const rosidl_runtime_c__type_description__TypeDescription * description) +{ + RCUTILS_CHECK_ARGUMENT_FOR_NULL(description, RCUTILS_RET_INVALID_ARGUMENT); + + rcutils_ret_t ret = RCUTILS_RET_ERROR; + + ret = rosidl_runtime_c_type_description_utils_individual_type_description_is_valid( + &description->type_description); + if (ret != RCUTILS_RET_OK) { + rcutils_error_string_t error_string = rcutils_get_error_string(); + rcutils_reset_error(); + if (description->type_description.type_name.size != 0) { + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Type description `%s` is invalid: Main type description is invalid:\n%s", + description->type_description.type_name.data, + error_string.str); + } else { + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Type description is invalid: Main type description is invalid:\n%s", error_string.str); + } + return ret; + } + + rcutils_hash_map_t * referenced_types_map = NULL; + rcutils_allocator_t allocator = rcutils_get_default_allocator(); + + ret = rosidl_runtime_c_type_description_utils_get_referenced_type_description_map( + &description->referenced_type_descriptions, &allocator, &referenced_types_map); + if (ret != RCUTILS_RET_OK) { + rcutils_error_string_t error_string = rcutils_get_error_string(); + rcutils_reset_error(); + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Could not construct referenced type description map:\n%s", error_string.str); + return false; + } + + size_t map_length; + + // Check no duplicated ref types + ret = rcutils_hash_map_get_size(referenced_types_map, &map_length); + if (ret != RCUTILS_RET_OK) { + rcutils_error_string_t error_string = rcutils_get_error_string(); + rcutils_reset_error(); + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Could not get size of referenced type description map for validation:\n%s", + error_string.str); + goto end_ref; + } + if (description->referenced_type_descriptions.size != map_length) { + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Type description `%s` is invalid: Duplicate referenced type descriptions", + description->type_description.type_name.data); + ret = RCUTILS_RET_INVALID_ARGUMENT; + goto end_ref; + } + + // Check no missing necessary ref types + rcutils_hash_map_t * necessary_types_map = NULL; + ret = rosidl_runtime_c_type_description_utils_get_necessary_referenced_type_descriptions_map( + &description->type_description, referenced_types_map, &allocator, &necessary_types_map); + if (ret != RCUTILS_RET_OK) { + rcutils_error_string_t error_string = rcutils_get_error_string(); + rcutils_reset_error(); + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Could not construct necessary referenced type description map:\n%s", error_string.str); + goto end_ref; + } + + // Check no unnecessary ref types + ret = rcutils_hash_map_get_size(necessary_types_map, &map_length); + if (ret != RCUTILS_RET_OK) { + rcutils_error_string_t error_string = rcutils_get_error_string(); + rcutils_reset_error(); + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Could not get size of necessary referenced type description map for validation:\n%s", + error_string.str); + goto end_necessary; + } + + if (description->referenced_type_descriptions.size != map_length) { + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Type description `%s` is invalid: Unnecessary referenced type descriptions", + description->type_description.type_name.data); + ret = RCUTILS_RET_INVALID_ARGUMENT; + goto end_necessary; + } + + // Check all referenced types valid (the prior checks ensure all of them are necessary) + for (size_t i = 0; i < description->referenced_type_descriptions.size; ++i) { + ret = rosidl_runtime_c_type_description_utils_individual_type_description_is_valid( + &description->referenced_type_descriptions.data[i]); + if (ret != RCUTILS_RET_OK) { + rcutils_error_string_t error_string = rcutils_get_error_string(); + rcutils_reset_error(); + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Type description `%s` is invalid: Invalid referenced type description:\n%s", + description->type_description.type_name.data, + error_string.str); + goto end_necessary; + } + } + + // Check referenced types sorted + rosidl_runtime_c__type_description__IndividualTypeDescription__Sequence * sorted_sequence = + rosidl_runtime_c__type_description__IndividualTypeDescription__Sequence__create(map_length); + if (sorted_sequence == NULL) { + rcutils_error_string_t error_string = rcutils_get_error_string(); + rcutils_reset_error(); + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Could allocate sequence for copy of referenced type descriptions:\n%s", error_string.str); + ret = RCUTILS_RET_BAD_ALLOC; + goto end_necessary; + } + if (!rosidl_runtime_c__type_description__IndividualTypeDescription__Sequence__copy( + &description->referenced_type_descriptions, sorted_sequence)) + { + rcutils_error_string_t error_string = rcutils_get_error_string(); + rcutils_reset_error(); + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Could not copy referenced type descriptions for validation:\n%s", error_string.str); + ret = RCUTILS_RET_BAD_ALLOC; + goto end_sequence; + } + ret = rosidl_runtime_c_type_description_utils_sort_referenced_type_descriptions_in_place( + sorted_sequence); + if (ret != RCUTILS_RET_OK) { + rcutils_error_string_t error_string = rcutils_get_error_string(); + rcutils_reset_error(); + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Could not sort copy of referenced type descriptions for validation:\n%s", error_string.str); + goto end_sequence; + } + + if (!rosidl_runtime_c__type_description__IndividualTypeDescription__Sequence__are_equal( + &description->referenced_type_descriptions, sorted_sequence)) + { + rcutils_error_string_t error_string = rcutils_get_error_string(); + rcutils_reset_error(); + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Type description `%s` is invalid: Referenced type descriptions not sorted:\n%s", + description->type_description.type_name.data, + error_string.str); + ret = RCUTILS_RET_INVALID_ARGUMENT; + goto end_sequence; + } + + return RCUTILS_RET_OK; + +end_sequence: + rosidl_runtime_c__type_description__IndividualTypeDescription__Sequence__destroy(sorted_sequence); + +end_necessary: + if (rcutils_hash_map_fini(necessary_types_map) != RCUTILS_RET_OK) { + RCUTILS_SAFE_FWRITE_TO_STDERR( + "While handling another error, failed to finalize necessary referenced types map"); + } + allocator.deallocate(necessary_types_map, allocator.state); + +end_ref: + if (rcutils_hash_map_fini(referenced_types_map) != RCUTILS_RET_OK) { + RCUTILS_SAFE_FWRITE_TO_STDERR( + "While handling another error, failed to finalize referenced types map"); + } + allocator.deallocate(referenced_types_map, allocator.state); + + return ret; +} + +rcutils_ret_t +rosidl_runtime_c_type_description_utils_coerce_to_valid_type_description_in_place( + rosidl_runtime_c__type_description__TypeDescription * type_description) +{ + rcutils_ret_t ret = rosidl_runtime_c_type_description_utils_individual_type_description_is_valid( + &type_description->type_description); + if (ret != RCUTILS_RET_OK) { + rcutils_error_string_t error_string = rcutils_get_error_string(); + rcutils_reset_error(); + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Could not make type description valid: Invalid main type description:\n%s", + error_string.str); + return ret; + } + + ret = rosidl_runtime_c_type_description_utils_prune_referenced_type_descriptions_in_place( + &type_description->type_description, &type_description->referenced_type_descriptions); + if (ret != RCUTILS_RET_OK) { + rcutils_error_string_t error_string = rcutils_get_error_string(); + rcutils_reset_error(); + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Could not make type description valid: Could not prune referenced_type_descriptions:\n%s", + error_string.str); + return ret; + } + + ret = rosidl_runtime_c_type_description_utils_sort_referenced_type_descriptions_in_place( + &type_description->referenced_type_descriptions); + if (ret != RCUTILS_RET_OK) { + rcutils_error_string_t error_string = rcutils_get_error_string(); + rcutils_reset_error(); + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Could not make type description valid: Could not sort referenced_type_descriptions:\n%s", + error_string.str); + return ret; + } + + return RCUTILS_RET_OK; +} + + +// ================================================================================================= +// DESCRIPTION CONSTRUCTION +// ================================================================================================= +rcutils_ret_t +rosidl_runtime_c_type_description_utils_create_field( + const char * name, size_t name_length, + uint8_t type_id, uint64_t capacity, uint64_t string_capacity, + const char * nested_type_name, size_t nested_type_name_length, + const char * default_value, size_t default_value_length, + rosidl_runtime_c__type_description__Field ** field) +{ + RCUTILS_CHECK_ARGUMENT_FOR_NULL(name, RCUTILS_RET_INVALID_ARGUMENT); + RCUTILS_CHECK_ARGUMENT_FOR_NULL(nested_type_name, RCUTILS_RET_INVALID_ARGUMENT); + RCUTILS_CHECK_ARGUMENT_FOR_NULL(default_value, RCUTILS_RET_INVALID_ARGUMENT); + if (*field != NULL) { + RCUTILS_SET_ERROR_MSG("'field' output argument is not pointing to null"); + return RCUTILS_RET_INVALID_ARGUMENT; + } + + *field = rosidl_runtime_c__type_description__Field__create(); + if (*field == NULL) { + RCUTILS_SET_ERROR_MSG( + "Could not create field"); + return RCUTILS_RET_BAD_ALLOC; + } + + // Field + if (!rosidl_runtime_c__String__assignn(&(*field)->name, name, name_length)) { + RCUTILS_SET_ERROR_MSG( + "Could not assign field name"); + rosidl_runtime_c__type_description__Field__destroy( + *field); + *field = NULL; + return RCUTILS_RET_BAD_ALLOC; + } + if (!rosidl_runtime_c__String__assignn( + &(*field)->default_value, default_value, default_value_length)) + { + RCUTILS_SET_ERROR_MSG( + "Could not assign field default value"); + rosidl_runtime_c__type_description__Field__destroy(*field); + *field = NULL; + return RCUTILS_RET_BAD_ALLOC; + } + + // FieldType + (*field)->type.type_id = type_id; + (*field)->type.capacity = capacity; + (*field)->type.string_capacity = string_capacity; + + if (!rosidl_runtime_c__String__assignn( + &(*field)->type.nested_type_name, nested_type_name, nested_type_name_length)) + { + RCUTILS_SET_ERROR_MSG( + "Could not assign field nested type name"); + rosidl_runtime_c__type_description__Field__destroy(*field); + *field = NULL; + return RCUTILS_RET_BAD_ALLOC; + } + + return RCUTILS_RET_OK; +} + +rcutils_ret_t +rosidl_runtime_c_type_description_utils_create_individual_type_description( + const char * type_name, size_t type_name_length, + rosidl_runtime_c__type_description__IndividualTypeDescription ** individual_description) +{ + RCUTILS_CHECK_ARGUMENT_FOR_NULL(type_name, RCUTILS_RET_INVALID_ARGUMENT); + if (*individual_description != NULL) { + RCUTILS_SET_ERROR_MSG("'individual_description' output argument is not pointing to null"); + return RCUTILS_RET_INVALID_ARGUMENT; + } + + *individual_description = rosidl_runtime_c__type_description__IndividualTypeDescription__create(); + if (*individual_description == NULL) { + RCUTILS_SET_ERROR_MSG( + "Could not create individual description"); + return RCUTILS_RET_BAD_ALLOC; + } + + if (!rosidl_runtime_c__String__assignn( + &(*individual_description)->type_name, type_name, type_name_length)) + { + RCUTILS_SET_ERROR_MSG( + "Could not assign individual description type name"); + rosidl_runtime_c__type_description__IndividualTypeDescription__destroy(*individual_description); + *individual_description = NULL; + return RCUTILS_RET_BAD_ALLOC; + } + return RCUTILS_RET_OK; +} + +rcutils_ret_t +rosidl_runtime_c_type_description_utils_create_type_description( + const char * type_name, size_t type_name_length, + rosidl_runtime_c__type_description__TypeDescription ** type_description) +{ + RCUTILS_CHECK_ARGUMENT_FOR_NULL(type_name, RCUTILS_RET_INVALID_ARGUMENT); + if (*type_description != NULL) { + RCUTILS_SET_ERROR_MSG("'type_description' output argument is not pointing to null"); + return RCUTILS_RET_INVALID_ARGUMENT; + } + + *type_description = rosidl_runtime_c__type_description__TypeDescription__create(); + if (*type_description == NULL) { + RCUTILS_SET_ERROR_MSG( + "Could not create type description"); + return RCUTILS_RET_BAD_ALLOC; + } + + if (!rosidl_runtime_c__String__assignn( + &(*type_description)->type_description.type_name, type_name, type_name_length)) + { + RCUTILS_SET_ERROR_MSG( + "Could not assign main individual description type name"); + rosidl_runtime_c__type_description__TypeDescription__destroy(*type_description); + *type_description = NULL; + return RCUTILS_RET_BAD_ALLOC; + } + return RCUTILS_RET_OK; +} + +rcutils_ret_t +rosidl_runtime_c_type_description_utils_append_field( + rosidl_runtime_c__type_description__IndividualTypeDescription * individual_type_description, + rosidl_runtime_c__type_description__Field * field) +{ + RCUTILS_CHECK_ARGUMENT_FOR_NULL(individual_type_description, RCUTILS_RET_INVALID_ARGUMENT); + RCUTILS_CHECK_ARGUMENT_FOR_NULL(field, RCUTILS_RET_INVALID_ARGUMENT); + + rcutils_allocator_t allocator = rcutils_get_default_allocator(); + rcutils_ret_t ret = RCUTILS_RET_ERROR; + + size_t allocation_size = ( + (individual_type_description->fields.size + 1) * + sizeof(rosidl_runtime_c__type_description__Field) + ); + size_t last_index = individual_type_description->fields.size; + rosidl_runtime_c__type_description__Field * next_ptr = allocator.reallocate( + individual_type_description->fields.data, allocation_size, allocator.state); + if (next_ptr == NULL && allocation_size != 0) { + RCUTILS_SET_ERROR_MSG( + "Could not realloc individual type description fields sequence"); + return RCUTILS_RET_BAD_ALLOC; + } + individual_type_description->fields.data = next_ptr; + individual_type_description->fields.size += 1; + individual_type_description->fields.capacity += 1; + + if (!rosidl_runtime_c__type_description__Field__init(&next_ptr[last_index])) { + RCUTILS_SET_ERROR_MSG( + "Could not init new individual type description field element"); + ret = RCUTILS_RET_BAD_ALLOC; + goto fail; + } + + if (!rosidl_runtime_c__type_description__Field__copy(field, &next_ptr[last_index])) { + RCUTILS_SET_ERROR_MSG( + "Could not copy into new individual type description field element"); + rosidl_runtime_c__type_description__Field__fini( + &next_ptr[last_index]); + ret = RCUTILS_RET_ERROR; + goto fail; + } + + return RCUTILS_RET_OK; + +fail: + // Attempt to undo on failure + next_ptr = allocator.reallocate( + individual_type_description->fields.data, + individual_type_description->fields.size * sizeof(rosidl_runtime_c__type_description__Field), + allocator.state); + if (next_ptr == NULL && individual_type_description->fields.size != 0) { + RCUTILS_SET_ERROR_MSG( + "Could not shorten individual type description fields sequence. " + "Excess memory will be UNINITIALIZED."); + } else { + individual_type_description->fields.data = next_ptr; + individual_type_description->fields.size -= 1; + individual_type_description->fields.capacity -= 1; + } + return ret; +} + +rcutils_ret_t +rosidl_runtime_c_type_description_utils_append_referenced_individual_type_description( + rosidl_runtime_c__type_description__TypeDescription * type_description, + rosidl_runtime_c__type_description__IndividualTypeDescription * referenced_type_description, + bool sort) +{ + RCUTILS_CHECK_ARGUMENT_FOR_NULL(type_description, RCUTILS_RET_INVALID_ARGUMENT); + RCUTILS_CHECK_ARGUMENT_FOR_NULL(referenced_type_description, RCUTILS_RET_INVALID_ARGUMENT); + + rcutils_ret_t ret = RCUTILS_RET_ERROR; + rcutils_allocator_t allocator = rcutils_get_default_allocator(); + + size_t allocation_size = ( + (type_description->referenced_type_descriptions.size + 1) * + sizeof(rosidl_runtime_c__type_description__IndividualTypeDescription) + ); + size_t last_index = type_description->referenced_type_descriptions.size; + + rosidl_runtime_c__type_description__IndividualTypeDescription * next_ptr = allocator.reallocate( + type_description->referenced_type_descriptions.data, allocation_size, allocator.state); + if (next_ptr == NULL && allocation_size != 0) { + RCUTILS_SET_ERROR_MSG( + "Could not realloc type description referenced type descriptions sequence"); + return RCUTILS_RET_BAD_ALLOC; + } + type_description->referenced_type_descriptions.data = next_ptr; + type_description->referenced_type_descriptions.size += 1; + type_description->referenced_type_descriptions.capacity += 1; + + if (!rosidl_runtime_c__type_description__IndividualTypeDescription__init(&next_ptr[last_index])) { + RCUTILS_SET_ERROR_MSG( + "Could not init new type description referenced type descriptions element"); + ret = RCUTILS_RET_BAD_ALLOC; + goto fail; + } + + if (!rosidl_runtime_c__type_description__IndividualTypeDescription__copy( + referenced_type_description, &next_ptr[last_index])) + { + // Attempt to undo changes on failure + RCUTILS_SET_ERROR_MSG( + "Could not copy into new type description referenced type descriptions element"); + rosidl_runtime_c__type_description__IndividualTypeDescription__fini(&next_ptr[last_index]); + ret = RCUTILS_RET_ERROR; + goto fail; + } + + if (sort) { + ret = rosidl_runtime_c_type_description_utils_sort_referenced_type_descriptions_in_place( + &type_description->referenced_type_descriptions); + if (ret != RCUTILS_RET_OK) { + RCUTILS_SET_ERROR_MSG("Could not sort copy of referenced type descriptions for validation"); + return ret; + } + } + return RCUTILS_RET_OK; + +fail: + // Attempt to undo on failure + next_ptr = allocator.reallocate( + type_description->referenced_type_descriptions.data, + ( + type_description->referenced_type_descriptions.size * + sizeof(rosidl_runtime_c__type_description__IndividualTypeDescription) + ), + allocator.state); + if (next_ptr == NULL && type_description->referenced_type_descriptions.size != 0) { + rcutils_error_string_t error_string = rcutils_get_error_string(); + rcutils_reset_error(); + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Could not shorten type description referenced type descriptions sequence. " + "Excess memory will be UNINITIALIZED:\n%s", + error_string.str); + } else { + type_description->referenced_type_descriptions.data = next_ptr; + type_description->referenced_type_descriptions.size -= 1; + type_description->referenced_type_descriptions.capacity -= 1; + } + return ret; +} + +rcutils_ret_t +rosidl_runtime_c_type_description_utils_append_referenced_type_description( + rosidl_runtime_c__type_description__TypeDescription * type_description, + rosidl_runtime_c__type_description__TypeDescription * type_description_to_append, + bool coerce_to_valid) +{ + RCUTILS_CHECK_ARGUMENT_FOR_NULL(type_description, RCUTILS_RET_INVALID_ARGUMENT); + RCUTILS_CHECK_ARGUMENT_FOR_NULL(type_description_to_append, RCUTILS_RET_INVALID_ARGUMENT); + + rcutils_allocator_t allocator = rcutils_get_default_allocator(); + rcutils_ret_t fini_ret = RCUTILS_RET_ERROR; + + // +1 for the type_description_to_append's main type description + size_t extend_count = type_description_to_append->referenced_type_descriptions.size + 1; + size_t allocation_size = ( + (type_description->referenced_type_descriptions.size + extend_count) * + sizeof(rosidl_runtime_c__type_description__IndividualTypeDescription) + ); + rosidl_runtime_c__type_description__IndividualTypeDescription * next_ptr = allocator.reallocate( + type_description->referenced_type_descriptions.data, allocation_size, allocator.state); + if (next_ptr == NULL && allocation_size != 0) { + RCUTILS_SET_ERROR_MSG( + "Could not realloc type description referenced type descriptions sequence"); + return RCUTILS_RET_BAD_ALLOC; + } + + size_t init_reset_size = 0; + size_t last_index = type_description->referenced_type_descriptions.size; + for (size_t i = last_index; i < last_index + extend_count; ++i) { + if (!rosidl_runtime_c__type_description__IndividualTypeDescription__init(&next_ptr[i])) { + RCUTILS_SET_ERROR_MSG( + "Could not init new type description referenced type descriptions element"); + fini_ret = RCUTILS_RET_BAD_ALLOC; + goto fail; + } + init_reset_size += 1; + } + + // Copy type_description_to_append's main type description + if (!rosidl_runtime_c__type_description__IndividualTypeDescription__copy( + &type_description_to_append->type_description, &next_ptr[last_index])) + { + RCUTILS_SET_ERROR_MSG( + "Could not copy into new type description referenced type descriptions element"); + fini_ret = RCUTILS_RET_ERROR; + goto fail; + } + + // Copy type_description_to_append's referenced type descriptions + // There are (extend_count - 1) referenced type descriptions to copy + for (size_t i = last_index + 1; i < last_index + extend_count; ++i) { + if (!rosidl_runtime_c__type_description__IndividualTypeDescription__copy( + &type_description_to_append->referenced_type_descriptions.data[i - 1 - last_index], + &next_ptr[i])) + { + RCUTILS_SET_ERROR_MSG( + "Could not copy new type description referenced type descriptions element"); + fini_ret = RCUTILS_RET_BAD_ALLOC; + goto fail; + } + init_reset_size += 1; + } + + type_description->referenced_type_descriptions.data = next_ptr; + type_description->referenced_type_descriptions.size += extend_count; + type_description->referenced_type_descriptions.capacity += extend_count; + + if (coerce_to_valid) { + rcutils_ret_t ret = + rosidl_runtime_c_type_description_utils_coerce_to_valid_type_description_in_place( + type_description); + if (ret != RCUTILS_RET_OK) { + rcutils_error_string_t error_string = rcutils_get_error_string(); + rcutils_reset_error(); + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Could not coerce type description to valid:\n%s", error_string.str); + return RCUTILS_RET_WARN; + } + } + + return RCUTILS_RET_OK; + +fail: + // Attempt to undo changes on failure + for (size_t j = last_index; j < last_index + init_reset_size; j++) { + rosidl_runtime_c__type_description__IndividualTypeDescription__fini(&next_ptr[j]); + } + next_ptr = allocator.reallocate( + type_description->referenced_type_descriptions.data, + ( + type_description->referenced_type_descriptions.size * + sizeof(rosidl_runtime_c__type_description__IndividualTypeDescription) + ), + allocator.state); + if (next_ptr == NULL && type_description->referenced_type_descriptions.size != 0) { + RCUTILS_SET_ERROR_MSG( + "Could not shorten type description referenced type descriptions sequence. " + "Excess memory will be UNINITIALIZED."); + type_description->referenced_type_descriptions.size += extend_count; + type_description->referenced_type_descriptions.capacity += extend_count; + } + return fini_ret; +} + +rcutils_ret_t +rosidl_runtime_c_type_description_utils_get_referenced_type_description_as_type_description( + const rosidl_runtime_c__type_description__IndividualTypeDescription__Sequence * + referenced_descriptions, + const rosidl_runtime_c__type_description__IndividualTypeDescription * referenced_description, + rosidl_runtime_c__type_description__TypeDescription ** output_description, + bool coerce_to_valid) +{ + RCUTILS_CHECK_ARGUMENT_FOR_NULL(referenced_descriptions, RCUTILS_RET_INVALID_ARGUMENT); + RCUTILS_CHECK_ARGUMENT_FOR_NULL(referenced_description, RCUTILS_RET_INVALID_ARGUMENT); + if (*output_description != NULL) { + RCUTILS_SET_ERROR_MSG("'output_description' output argument is not pointing to null"); + return RCUTILS_RET_INVALID_ARGUMENT; + } + + *output_description = rosidl_runtime_c__type_description__TypeDescription__create(); + if (*output_description == NULL) { + RCUTILS_SET_ERROR_MSG("Could not create output type description"); + return RCUTILS_RET_BAD_ALLOC; + } + + if (!rosidl_runtime_c__type_description__IndividualTypeDescription__copy( + referenced_description, &(*output_description)->type_description)) + { + RCUTILS_SET_ERROR_MSG("Could not copy referenced type description into main description"); + rosidl_runtime_c__type_description__TypeDescription__destroy(*output_description); + *output_description = NULL; + return RCUTILS_RET_ERROR; + } + + if (!rosidl_runtime_c__type_description__IndividualTypeDescription__Sequence__copy( + referenced_descriptions, &(*output_description)->referenced_type_descriptions)) + { + RCUTILS_SET_ERROR_MSG("Could not copy referenced type descriptions"); + rosidl_runtime_c__type_description__TypeDescription__destroy(*output_description); + *output_description = NULL; + return RCUTILS_RET_ERROR; + } + + if (coerce_to_valid) { + rcutils_ret_t ret = + rosidl_runtime_c_type_description_utils_coerce_to_valid_type_description_in_place( + *output_description); + if (ret != RCUTILS_RET_OK) { + RCUTILS_SET_ERROR_MSG("Could not coerce output type description to valid"); + rosidl_runtime_c__type_description__TypeDescription__destroy(*output_description); + *output_description = NULL; + return ret; + } + } + return RCUTILS_RET_OK; +} + +rcutils_ret_t +rosidl_runtime_c_type_description_utils_repl_individual_type_description_type_names_in_place( + rosidl_runtime_c__type_description__IndividualTypeDescription * individual_type_description, + const char * from, + const char * to) +{ + RCUTILS_CHECK_ARGUMENT_FOR_NULL(individual_type_description, RCUTILS_RET_INVALID_ARGUMENT); + RCUTILS_CHECK_ARGUMENT_FOR_NULL(from, RCUTILS_RET_INVALID_ARGUMENT); + RCUTILS_CHECK_ARGUMENT_FOR_NULL(to, RCUTILS_RET_INVALID_ARGUMENT); + + rcutils_allocator_t allocator = rcutils_get_default_allocator(); + bool ret = false; + char * repl = NULL; + + // Replace type name + repl = rcutils_repl_str( + individual_type_description->type_name.data, from, to, &allocator); + if (repl == NULL) { + RCUTILS_SET_ERROR_MSG("Could not replace individual type description type name"); + return RCUTILS_RET_ERROR; + } + + ret = rosidl_runtime_c__String__assign(&(individual_type_description->type_name), repl); + allocator.deallocate(repl, allocator.state); + if (!ret) { + RCUTILS_SET_ERROR_MSG("Could not assign individual type description type name"); + return RCUTILS_RET_ERROR; + } + + // Replace field nested type names + rosidl_runtime_c__type_description__Field * field = NULL; + if (individual_type_description->fields.data) { + for (size_t i = 0; i < individual_type_description->fields.size; ++i) { + field = &individual_type_description->fields.data[i]; + if (!field->type.nested_type_name.size) { + continue; + } + + repl = rcutils_repl_str(field->type.nested_type_name.data, from, to, &allocator); + if (repl == NULL) { + RCUTILS_SET_ERROR_MSG( + "Could not replace individual type description field nested type name. Beware: " + "Partial in-place replacements might have already happened."); + return RCUTILS_RET_ERROR; + } + + ret = rosidl_runtime_c__String__assign(&(field->type.nested_type_name), repl); + allocator.deallocate(repl, allocator.state); + if (!ret) { + RCUTILS_SET_ERROR_MSG( + "Could not assign individual type description field nested type name. Beware: " + "Partial in-place replacements might have already happened."); + return RCUTILS_RET_ERROR; + } + } + } + return RCUTILS_RET_OK; +} + +rcutils_ret_t +rosidl_runtime_c_type_description_utils_repl_all_type_description_type_names_in_place( + rosidl_runtime_c__type_description__TypeDescription * type_description, + const char * from, + const char * to) +{ + RCUTILS_CHECK_ARGUMENT_FOR_NULL(type_description, RCUTILS_RET_INVALID_ARGUMENT); + RCUTILS_CHECK_ARGUMENT_FOR_NULL(from, RCUTILS_RET_INVALID_ARGUMENT); + RCUTILS_CHECK_ARGUMENT_FOR_NULL(to, RCUTILS_RET_INVALID_ARGUMENT); + + rcutils_ret_t ret = RCUTILS_RET_ERROR; + + /* *INDENT-OFF* */ // Uncrustify will dislodge the NOLINT + ret = rosidl_runtime_c_type_description_utils_repl_individual_type_description_type_names_in_place( // NOLINT + &type_description->type_description, from, to); + /* *INDENT-ON* */ + if (ret != RCUTILS_RET_OK) { + rcutils_error_string_t error_string = rcutils_get_error_string(); + rcutils_reset_error(); + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Could not replace main type description type name:\n%s", error_string.str); + return ret; + } + + if (type_description->referenced_type_descriptions.data) { + for (size_t i = 0; i < type_description->referenced_type_descriptions.size; ++i) { + rosidl_runtime_c__type_description__IndividualTypeDescription * individual_type_description = + &type_description->referenced_type_descriptions.data[i]; + + /* *INDENT-OFF* */ // Uncrustify will dislodge the NOLINT + ret = rosidl_runtime_c_type_description_utils_repl_individual_type_description_type_names_in_place( // NOLINT + individual_type_description, from, to); + /* *INDENT-ON* */ + + if (ret != RCUTILS_RET_OK) { + rcutils_error_string_t error_string = rcutils_get_error_string(); + rcutils_reset_error(); + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Could not replace type names in referenced type. Beware: " + "Partial in-place replacements might have already happened:\n%s", + error_string.str); + return ret; + } + } + } + return RCUTILS_RET_OK; +} diff --git a/rosidl_runtime_c/test/test_type_description_utils.cpp b/rosidl_runtime_c/test/test_type_description_utils.cpp new file mode 100644 index 000000000..101059cfd --- /dev/null +++ b/rosidl_runtime_c/test/test_type_description_utils.cpp @@ -0,0 +1,572 @@ +// 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 + +#include +#include +#include +#include + +#include + +#include "rosidl_runtime_c/type_description/field__functions.h" +#include "rosidl_runtime_c/type_description/field__struct.h" +#include "rosidl_runtime_c/type_description/individual_type_description__functions.h" +#include "rosidl_runtime_c/type_description/individual_type_description__struct.h" +#include "rosidl_runtime_c/type_description/type_description__functions.h" +#include "rosidl_runtime_c/type_description/type_description__struct.h" +#include "rosidl_runtime_c/type_description_utils.h" + + +TEST(TestUtils, test_basic_construction) +{ + const std::string test_name = "test_name"; + + // TypeDescription + { + rosidl_runtime_c__type_description__TypeDescription * desc = + rosidl_runtime_c__type_description__TypeDescription__create(); + ASSERT_TRUE(desc); + ASSERT_TRUE( + rosidl_runtime_c__String__assignn( + &desc->type_description.type_name, test_name.c_str(), test_name.size())); + + rosidl_runtime_c__type_description__TypeDescription * utils_desc = NULL; + EXPECT_EQ( + rosidl_runtime_c_type_description_utils_create_type_description( + test_name.c_str(), + test_name.size(), &utils_desc), + RCUTILS_RET_OK); + ASSERT_TRUE(utils_desc); + + EXPECT_FALSE(utils_desc->type_description.fields.data); + EXPECT_EQ(utils_desc->type_description.fields.size, 0); + EXPECT_EQ(utils_desc->type_description.fields.capacity, 0); + + EXPECT_FALSE(utils_desc->referenced_type_descriptions.data); + EXPECT_EQ(utils_desc->referenced_type_descriptions.size, 0); + EXPECT_EQ(utils_desc->referenced_type_descriptions.capacity, 0); + + EXPECT_TRUE(rosidl_runtime_c__type_description__TypeDescription__are_equal(desc, utils_desc)); + + rosidl_runtime_c__type_description__TypeDescription__destroy(desc); + rosidl_runtime_c__type_description__TypeDescription__destroy(utils_desc); + } + + // IndividualTypeDescription + { + rosidl_runtime_c__type_description__IndividualTypeDescription * individual_desc = + rosidl_runtime_c__type_description__IndividualTypeDescription__create(); + ASSERT_TRUE(individual_desc); + ASSERT_TRUE( + rosidl_runtime_c__String__assignn( + &individual_desc->type_name, test_name.c_str(), test_name.size())); + + rosidl_runtime_c__type_description__IndividualTypeDescription * individual_utils_desc = NULL; + EXPECT_EQ( + rosidl_runtime_c_type_description_utils_create_individual_type_description( + test_name.c_str(), test_name.size(), &individual_utils_desc), + RCUTILS_RET_OK); + + ASSERT_TRUE(individual_utils_desc); + + EXPECT_FALSE(individual_utils_desc->fields.data); + EXPECT_EQ(individual_utils_desc->fields.size, 0); + EXPECT_EQ(individual_utils_desc->fields.capacity, 0); + + EXPECT_TRUE( + rosidl_runtime_c__type_description__IndividualTypeDescription__are_equal( + individual_desc, individual_utils_desc)); + + rosidl_runtime_c__type_description__IndividualTypeDescription__destroy(individual_desc); + rosidl_runtime_c__type_description__IndividualTypeDescription__destroy(individual_utils_desc); + } + + // Field + { + rosidl_runtime_c__type_description__Field * field = + rosidl_runtime_c__type_description__Field__create(); + ASSERT_TRUE(field); + ASSERT_TRUE( + rosidl_runtime_c__String__assignn( + &field->name, test_name.c_str(), test_name.size())); + + rosidl_runtime_c__type_description__Field * utils_field = NULL; + EXPECT_EQ( + rosidl_runtime_c_type_description_utils_create_field( + test_name.c_str(), test_name.size(), 0, // Name, Name Length, Type ID + 0, 0, // Capacity, String Capacity + "", 0, // Nested Type Name, Nested Type Name Length + "", 0, // Default Value, Default Value Length + &utils_field), + RCUTILS_RET_OK); + ASSERT_TRUE(utils_field); + + EXPECT_TRUE(utils_field->default_value.data); + + EXPECT_EQ(utils_field->default_value.size, 0); + EXPECT_EQ(utils_field->default_value.capacity, 1); // Null terminator + + EXPECT_TRUE(rosidl_runtime_c__type_description__Field__are_equal(field, utils_field)); + + rosidl_runtime_c__type_description__Field__destroy(field); + rosidl_runtime_c__type_description__Field__destroy(utils_field); + } +} + + +class TestUtilsFixture : public ::testing::Test +{ +public: + void SetUp() + { + type_description_1 = NULL; + rosidl_runtime_c_type_description_utils_create_type_description("t1", 2, &type_description_1); + ASSERT_TRUE(type_description_1); + + type_description_2 = NULL; + rosidl_runtime_c_type_description_utils_create_type_description("t2", 2, &type_description_2); + ASSERT_TRUE(type_description_2); + + type_description_3 = NULL; + rosidl_runtime_c_type_description_utils_create_type_description("t3", 2, &type_description_3); + ASSERT_TRUE(type_description_3); + + + individual_desc_1 = NULL; + rosidl_runtime_c_type_description_utils_create_individual_type_description( + "i1", 2, &individual_desc_1); + ASSERT_TRUE(individual_desc_1); + + individual_desc_2 = NULL; + rosidl_runtime_c_type_description_utils_create_individual_type_description( + "i2", 2, &individual_desc_2); + ASSERT_TRUE(individual_desc_2); + + individual_desc_3 = NULL; + rosidl_runtime_c_type_description_utils_create_individual_type_description( + "i3", 2, &individual_desc_3); + ASSERT_TRUE(individual_desc_3); + + empty_individual_desc = NULL; + rosidl_runtime_c_type_description_utils_create_individual_type_description( + "empty", 5, &empty_individual_desc); + ASSERT_TRUE(empty_individual_desc); + + + field_1 = NULL; // Nested + rosidl_runtime_c_type_description_utils_create_field( + "f1", 2, 1, // Name, Name Length, Type ID + 0, 0, // Capacity, String Capacity + "empty", 5, // Nested Type Name, Nested Type Name Length + "", 0, // Default Value, Default Value Length + &field_1); + ASSERT_TRUE(field_1); + + field_2 = NULL; + rosidl_runtime_c_type_description_utils_create_field( + "f2", 2, 2, // Name, Name Length, Type ID + 0, 0, "", 0, "", 0, &field_2); + ASSERT_TRUE(field_2); + + field_3 = NULL; + rosidl_runtime_c_type_description_utils_create_field( + "f3", 2, 3, // Name, Name Length, Type ID + 0, 0, "", 0, "", 0, &field_3); + ASSERT_TRUE(field_3); + } + + void TearDown() + { + rosidl_runtime_c__type_description__TypeDescription__destroy(type_description_1); + rosidl_runtime_c__type_description__TypeDescription__destroy(type_description_2); + rosidl_runtime_c__type_description__TypeDescription__destroy(type_description_3); + rosidl_runtime_c__type_description__IndividualTypeDescription__destroy(individual_desc_1); + rosidl_runtime_c__type_description__IndividualTypeDescription__destroy(individual_desc_2); + rosidl_runtime_c__type_description__IndividualTypeDescription__destroy(individual_desc_3); + rosidl_runtime_c__type_description__IndividualTypeDescription__destroy(empty_individual_desc); + rosidl_runtime_c__type_description__Field__destroy(field_1); + rosidl_runtime_c__type_description__Field__destroy(field_2); + rosidl_runtime_c__type_description__Field__destroy(field_3); + } + + rosidl_runtime_c__type_description__TypeDescription * type_description_1; + rosidl_runtime_c__type_description__TypeDescription * type_description_2; + rosidl_runtime_c__type_description__TypeDescription * type_description_3; + + rosidl_runtime_c__type_description__IndividualTypeDescription * individual_desc_1; + rosidl_runtime_c__type_description__IndividualTypeDescription * individual_desc_2; + rosidl_runtime_c__type_description__IndividualTypeDescription * individual_desc_3; + rosidl_runtime_c__type_description__IndividualTypeDescription * empty_individual_desc; + + rosidl_runtime_c__type_description__Field * field_1; + rosidl_runtime_c__type_description__Field * field_2; + rosidl_runtime_c__type_description__Field * field_3; + + rcutils_ret_t ret = RCUTILS_RET_ERROR; +}; + + +TEST_F(TestUtilsFixture, test_appends_and_advanced_construction) +{ + // FIELD APPEND + EXPECT_EQ(individual_desc_1->fields.size, 0); + EXPECT_EQ(individual_desc_1->fields.capacity, 0); + ret = rosidl_runtime_c_type_description_utils_append_field(individual_desc_1, field_1); + EXPECT_EQ(ret, RCUTILS_RET_OK); + EXPECT_EQ(individual_desc_1->fields.size, 1); + EXPECT_EQ(individual_desc_1->fields.capacity, 1); + + ret = rosidl_runtime_c_type_description_utils_append_field(individual_desc_1, field_2); + EXPECT_EQ(ret, RCUTILS_RET_OK); + EXPECT_EQ(individual_desc_1->fields.size, 2); + EXPECT_EQ(individual_desc_1->fields.capacity, 2); + + ret = rosidl_runtime_c_type_description_utils_append_field(individual_desc_1, field_3); + EXPECT_EQ(ret, RCUTILS_RET_OK); + EXPECT_EQ(individual_desc_1->fields.size, 3); + EXPECT_EQ(individual_desc_1->fields.capacity, 3); + + EXPECT_TRUE( + rosidl_runtime_c__type_description__Field__are_equal( + &individual_desc_1->fields.data[0], field_1)); + EXPECT_TRUE( + rosidl_runtime_c__type_description__Field__are_equal( + &individual_desc_1->fields.data[1], field_2)); + EXPECT_TRUE( + rosidl_runtime_c__type_description__Field__are_equal( + &individual_desc_1->fields.data[2], field_3)); + + + // REFERENCED INDIVIDUAL TYPE DESCRIPTION APPEND + // Append out of order + EXPECT_EQ(type_description_1->referenced_type_descriptions.size, 0); + EXPECT_EQ(type_description_1->referenced_type_descriptions.capacity, 0); + ret = rosidl_runtime_c_type_description_utils_append_referenced_individual_type_description( + type_description_1, individual_desc_1, false); + ASSERT_EQ(ret, RCUTILS_RET_OK); + EXPECT_EQ(type_description_1->referenced_type_descriptions.size, 1); + EXPECT_EQ(type_description_1->referenced_type_descriptions.capacity, 1); + + ret = rosidl_runtime_c_type_description_utils_append_referenced_individual_type_description( + type_description_1, individual_desc_3, false); + ASSERT_EQ(ret, RCUTILS_RET_OK); + EXPECT_EQ(type_description_1->referenced_type_descriptions.size, 2); + EXPECT_EQ(type_description_1->referenced_type_descriptions.capacity, 2); + + EXPECT_TRUE( + rosidl_runtime_c__type_description__IndividualTypeDescription__are_equal( + &type_description_1->referenced_type_descriptions.data[0], individual_desc_1)); + + EXPECT_TRUE( + rosidl_runtime_c__type_description__IndividualTypeDescription__are_equal( + &type_description_1->referenced_type_descriptions.data[1], individual_desc_3)); + + + // Append and Sort + ret = rosidl_runtime_c_type_description_utils_append_referenced_individual_type_description( + type_description_1, individual_desc_2, true); + ASSERT_EQ(ret, RCUTILS_RET_OK); + EXPECT_EQ(type_description_1->referenced_type_descriptions.size, 3); + EXPECT_EQ(type_description_1->referenced_type_descriptions.capacity, 3); + + EXPECT_TRUE( + rosidl_runtime_c__type_description__IndividualTypeDescription__are_equal( + &type_description_1->referenced_type_descriptions.data[0], individual_desc_1)); + + EXPECT_TRUE( + rosidl_runtime_c__type_description__IndividualTypeDescription__are_equal( + &type_description_1->referenced_type_descriptions.data[1], individual_desc_2)); + + EXPECT_TRUE( + rosidl_runtime_c__type_description__IndividualTypeDescription__are_equal( + &type_description_1->referenced_type_descriptions.data[2], individual_desc_3)); + + + // REFERENCED TYPE DESCRIPTION APPEND + // Naive recursive append + EXPECT_EQ(type_description_2->referenced_type_descriptions.size, 0); + EXPECT_EQ(type_description_2->referenced_type_descriptions.capacity, 0); + ret = rosidl_runtime_c_type_description_utils_append_referenced_type_description( + type_description_2, type_description_1, false); + ASSERT_EQ(ret, RCUTILS_RET_OK); + + EXPECT_EQ(type_description_2->referenced_type_descriptions.size, 4); + EXPECT_EQ(type_description_2->referenced_type_descriptions.capacity, 4); + + + // Recursive append with coercion to valid + EXPECT_EQ( + rosidl_runtime_c_type_description_utils_append_field( + &type_description_1->type_description, field_1), + RCUTILS_RET_OK); + + EXPECT_EQ( + rosidl_runtime_c_type_description_utils_append_field( + &type_description_3->type_description, field_1), + RCUTILS_RET_OK); + + // Deliberately invalid + EXPECT_EQ(type_description_3->referenced_type_descriptions.size, 0); + EXPECT_EQ(type_description_3->referenced_type_descriptions.capacity, 0); + ASSERT_EQ( + rosidl_runtime_c_type_description_utils_append_referenced_type_description( + type_description_3, type_description_1, true), + RCUTILS_RET_WARN); + rcutils_reset_error(); + EXPECT_EQ(type_description_3->referenced_type_descriptions.size, 4); + EXPECT_EQ(type_description_3->referenced_type_descriptions.capacity, 4); + + // With the append of the empty ref description, it should work now + ret = rosidl_runtime_c_type_description_utils_append_referenced_individual_type_description( + type_description_1, empty_individual_desc, true); + ASSERT_EQ(ret, RCUTILS_RET_OK); + + ASSERT_EQ( + rosidl_runtime_c_type_description_utils_append_referenced_type_description( + type_description_3, type_description_1, true), + RCUTILS_RET_OK); + EXPECT_EQ(type_description_3->referenced_type_descriptions.size, 1); + EXPECT_EQ(type_description_3->referenced_type_descriptions.capacity, 1); + EXPECT_TRUE( + rosidl_runtime_c__type_description__IndividualTypeDescription__are_equal( + &type_description_3->referenced_type_descriptions.data[0], empty_individual_desc)); + + + // Get referenced type as its own full description + // Naively (naive append of ref types) + rosidl_runtime_c__type_description__TypeDescription * subset_desc = NULL; + rosidl_runtime_c_type_description_utils_get_referenced_type_description_as_type_description( + &type_description_1->referenced_type_descriptions, // Refs: i1, i2, i3, empty + individual_desc_1, // Depends on ref: "empty" + &subset_desc, + false); // No coercion to valid + EXPECT_EQ(subset_desc->referenced_type_descriptions.size, 4); + EXPECT_EQ(subset_desc->referenced_type_descriptions.capacity, 4); + EXPECT_FALSE( + rosidl_runtime_c__type_description__TypeDescription__are_equal( + type_description_1, subset_desc)); + rosidl_runtime_c__type_description__TypeDescription__destroy(subset_desc); + + rosidl_runtime_c__type_description__TypeDescription * subset_desc_2 = NULL; + rosidl_runtime_c_type_description_utils_get_referenced_type_description_as_type_description( + &type_description_1->referenced_type_descriptions, // Refs: i1, i2, i3, empty + individual_desc_1, // Depends on ref: "empty" + &subset_desc_2, + true); // Coercion to valid + EXPECT_EQ( + rosidl_runtime_c_type_description_utils_type_description_is_valid(subset_desc_2), + RCUTILS_RET_OK); + EXPECT_EQ(subset_desc_2->referenced_type_descriptions.size, 1); + EXPECT_EQ(subset_desc_2->referenced_type_descriptions.capacity, 1); + EXPECT_FALSE( + rosidl_runtime_c__type_description__TypeDescription__are_equal( + type_description_1, subset_desc_2)); + EXPECT_TRUE( + rosidl_runtime_c__type_description__IndividualTypeDescription__are_equal( + &subset_desc_2->referenced_type_descriptions.data[0], empty_individual_desc)); + + // Test type name string replacements + ret = rosidl_runtime_c_type_description_utils_repl_all_type_description_type_names_in_place( + subset_desc_2, "mpty", "ligibility" // Expect eligibility + ); + EXPECT_EQ(ret, RCUTILS_RET_OK); + EXPECT_FALSE( + rosidl_runtime_c__type_description__IndividualTypeDescription__are_equal( + &subset_desc_2->referenced_type_descriptions.data[0], empty_individual_desc)); + rosidl_runtime_c__type_description__TypeDescription__destroy(subset_desc_2); +} + + +TEST_F(TestUtilsFixture, test_find) +{ + // Find Field + ret = rosidl_runtime_c_type_description_utils_append_field(individual_desc_1, field_1); + ASSERT_EQ(ret, RCUTILS_RET_OK); + ret = rosidl_runtime_c_type_description_utils_append_field(individual_desc_1, field_2); + ASSERT_EQ(ret, RCUTILS_RET_OK); + ret = rosidl_runtime_c_type_description_utils_append_field(individual_desc_1, field_3); + ASSERT_EQ(ret, RCUTILS_RET_OK); + + rosidl_runtime_c__type_description__Field * find_field = NULL; + ret = rosidl_runtime_c_type_description_utils_find_field( + &individual_desc_1->fields, "a", + &find_field); + EXPECT_EQ(ret, RCUTILS_RET_NOT_FOUND); + rcutils_reset_error(); + EXPECT_FALSE(find_field); + + ret = rosidl_runtime_c_type_description_utils_find_field( + &individual_desc_1->fields, "f3", + &find_field); + EXPECT_EQ(ret, RCUTILS_RET_OK); + EXPECT_TRUE(rosidl_runtime_c__type_description__Field__are_equal(find_field, field_3)); + + // Find Referenced Type Description + ret = rosidl_runtime_c_type_description_utils_append_referenced_individual_type_description( + type_description_1, individual_desc_1, false); + ASSERT_EQ(ret, RCUTILS_RET_OK); + ret = rosidl_runtime_c_type_description_utils_append_referenced_individual_type_description( + type_description_1, individual_desc_2, false); + ASSERT_EQ(ret, RCUTILS_RET_OK); + ret = rosidl_runtime_c_type_description_utils_append_referenced_individual_type_description( + type_description_1, individual_desc_3, true); + ASSERT_EQ(ret, RCUTILS_RET_OK); + + rosidl_runtime_c__type_description__IndividualTypeDescription * find_desc = NULL; + ret = rosidl_runtime_c_type_description_utils_find_referenced_type_description( + &type_description_1->referenced_type_descriptions, "a", &find_desc); + EXPECT_EQ(ret, RCUTILS_RET_NOT_FOUND); + rcutils_reset_error(); + EXPECT_FALSE(find_desc); + + ret = rosidl_runtime_c_type_description_utils_find_referenced_type_description( + &type_description_1->referenced_type_descriptions, "i3", &find_desc); + EXPECT_EQ(ret, RCUTILS_RET_OK); + EXPECT_TRUE( + rosidl_runtime_c__type_description__IndividualTypeDescription__are_equal( + find_desc, individual_desc_3)); +} + + +TEST_F(TestUtilsFixture, test_maps) +{ + rcutils_hash_map_t * hash_map = NULL; + rcutils_hash_map_t * ref_types_hash_map = NULL; + rcutils_allocator_t allocator = rcutils_get_default_allocator(); + size_t map_length; + + // Field map when empty + ret = rosidl_runtime_c_type_description_utils_get_field_map( + individual_desc_1, &allocator, + &hash_map); + ASSERT_EQ(ret, RCUTILS_RET_OK); + + ASSERT_EQ(rcutils_hash_map_get_size(hash_map, &map_length), RCUTILS_RET_OK); + ASSERT_EQ(map_length, 0); + ASSERT_EQ(rcutils_hash_map_fini(hash_map), RCUTILS_RET_OK); + allocator.deallocate(hash_map, allocator.state); + hash_map = NULL; + + // Field map when populated + ret = rosidl_runtime_c_type_description_utils_append_field(individual_desc_1, field_1); + ASSERT_EQ(ret, RCUTILS_RET_OK); + ret = rosidl_runtime_c_type_description_utils_append_field(individual_desc_1, field_2); + ASSERT_EQ(ret, RCUTILS_RET_OK); + ret = rosidl_runtime_c_type_description_utils_append_field(individual_desc_1, field_3); + ASSERT_EQ(ret, RCUTILS_RET_OK); + + ret = rosidl_runtime_c_type_description_utils_get_field_map( + individual_desc_1, &allocator, + &hash_map); + ASSERT_EQ(ret, RCUTILS_RET_OK); + + ASSERT_EQ(rcutils_hash_map_get_size(hash_map, &map_length), RCUTILS_RET_OK); + ASSERT_EQ(map_length, 3); + ASSERT_EQ(rcutils_hash_map_fini(hash_map), RCUTILS_RET_OK); + allocator.deallocate(hash_map, allocator.state); + hash_map = NULL; + + + // Ref type map when empty + ret = rosidl_runtime_c_type_description_utils_get_referenced_type_description_map( + &type_description_1->referenced_type_descriptions, &allocator, &hash_map); + ASSERT_EQ(ret, RCUTILS_RET_OK); + + ASSERT_EQ(rcutils_hash_map_get_size(hash_map, &map_length), RCUTILS_RET_OK); + ASSERT_EQ(map_length, 0); + ASSERT_EQ(rcutils_hash_map_fini(hash_map), RCUTILS_RET_OK); + allocator.deallocate(hash_map, allocator.state); + hash_map = NULL; + + // Ref type map when populated + // Also we set up the next test block (for testing the necessary map) + ret = rosidl_runtime_c_type_description_utils_append_referenced_individual_type_description( + type_description_1, individual_desc_1, false); + ASSERT_EQ(ret, RCUTILS_RET_OK); + ret = rosidl_runtime_c_type_description_utils_append_referenced_individual_type_description( + type_description_1, individual_desc_2, false); + ASSERT_EQ(ret, RCUTILS_RET_OK); + ret = rosidl_runtime_c_type_description_utils_append_referenced_individual_type_description( + type_description_1, individual_desc_3, false); + ASSERT_EQ(ret, RCUTILS_RET_OK); + ret = rosidl_runtime_c_type_description_utils_append_referenced_individual_type_description( + type_description_1, empty_individual_desc, true); + ASSERT_EQ(ret, RCUTILS_RET_OK); + + ret = rosidl_runtime_c_type_description_utils_get_referenced_type_description_map( + &type_description_1->referenced_type_descriptions, &allocator, &ref_types_hash_map); + ASSERT_EQ(ret, RCUTILS_RET_OK); + + ASSERT_EQ(rcutils_hash_map_get_size(ref_types_hash_map, &map_length), RCUTILS_RET_OK); + ASSERT_EQ(map_length, 4); + + + // Necessary ref type map when empty + ret = rosidl_runtime_c_type_description_utils_get_necessary_referenced_type_descriptions_map( + &type_description_1->type_description, + ref_types_hash_map, + &allocator, + &hash_map); + ASSERT_EQ(ret, RCUTILS_RET_OK); + + ASSERT_EQ(rcutils_hash_map_get_size(hash_map, &map_length), RCUTILS_RET_OK); + ASSERT_EQ(map_length, 0); + ASSERT_EQ(rcutils_hash_map_fini(hash_map), RCUTILS_RET_OK); + allocator.deallocate(hash_map, allocator.state); + hash_map = NULL; + + // Necessary ref type map when populated + ret = rosidl_runtime_c_type_description_utils_append_field( + &type_description_1->type_description, field_1); + ASSERT_EQ(ret, RCUTILS_RET_OK); + + // Expect failure since empty ref type not added yet + ret = rosidl_runtime_c_type_description_utils_get_necessary_referenced_type_descriptions_map( + &type_description_1->type_description, + ref_types_hash_map, + &allocator, + &hash_map); + ASSERT_EQ(ret, RCUTILS_RET_OK); + + ASSERT_EQ(rcutils_hash_map_get_size(hash_map, &map_length), RCUTILS_RET_OK); + ASSERT_EQ(map_length, 1); + ASSERT_EQ(rcutils_hash_map_fini(hash_map), RCUTILS_RET_OK); + allocator.deallocate(hash_map, allocator.state); + hash_map = NULL; + + + // Ref type map to sequence + rosidl_runtime_c__type_description__IndividualTypeDescription__Sequence * seq = NULL; + /* *INDENT-OFF* */ + ret = rosidl_runtime_c_type_description_utils_copy_init_sequence_from_referenced_type_descriptions_map( // NOLINT + ref_types_hash_map, &seq, true); + /* *INDENT-ON* */ + EXPECT_EQ(ret, RCUTILS_RET_OK); + EXPECT_TRUE( + rosidl_runtime_c__type_description__IndividualTypeDescription__Sequence__are_equal( + &type_description_1->referenced_type_descriptions, seq)); + + + rosidl_runtime_c__type_description__IndividualTypeDescription__Sequence__destroy(seq); + + ASSERT_EQ(rcutils_hash_map_fini(ref_types_hash_map), RCUTILS_RET_OK); + allocator.deallocate(ref_types_hash_map, allocator.state); + ref_types_hash_map = NULL; +} + +// TODO(methylDragon) +// TEST(Utils, test_validity) <-- This is technically implicitly tested with appends with coercion