diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e0cc441..619d80c4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,7 @@ set(rcutils_sources src/string_map.c src/time.c ${time_impl_c} + src/uint8_array.c ) set_source_files_properties( ${rcutils_sources} @@ -304,6 +305,13 @@ if(BUILD_TESTING) if(TARGET test_snprintf) target_link_libraries(test_snprintf ${PROJECT_NAME}) endif() + + rcutils_custom_add_gtest(test_uint8_array + test/test_uint8_array.cpp + ) + if(TARGET test_uint8_array) + target_link_libraries(test_uint8_array ${PROJECT_NAME}) + endif() endif() ament_export_dependencies(ament_cmake) diff --git a/include/rcutils/types.h b/include/rcutils/types.h index 2d3666dd..9c0c41e8 100644 --- a/include/rcutils/types.h +++ b/include/rcutils/types.h @@ -24,6 +24,7 @@ extern "C" #include "rcutils/types/string_array.h" #include "rcutils/types/string_map.h" #include "rcutils/types/rcutils_ret.h" +#include "rcutils/types/uint8_array.h" #ifdef __cplusplus } diff --git a/include/rcutils/types/uint8_array.h b/include/rcutils/types/uint8_array.h new file mode 100644 index 00000000..cf767aac --- /dev/null +++ b/include/rcutils/types/uint8_array.h @@ -0,0 +1,110 @@ +// Copyright 2018 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef RCUTILS__TYPES__UINT8_ARRAY_H_ +#define RCUTILS__TYPES__UINT8_ARRAY_H_ + +#if __cplusplus +extern "C" +{ +#endif + +#include + +#include "rcutils/allocator.h" +#include "rcutils/types/rcutils_ret.h" +#include "rcutils/visibility_control.h" + +typedef struct RCUTILS_PUBLIC_TYPE rcutils_uint8_array_t +{ + uint8_t * buffer; + size_t buffer_length; + size_t buffer_capacity; + rcutils_allocator_t allocator; +} rcutils_uint8_array_t; + +/// Return a zero initialized uint8 array struct. +/** + * \return rcutils_uint8_array_t a zero initialized uint8 array struct + */ +RCUTILS_PUBLIC +RCUTILS_WARN_UNUSED +rcutils_uint8_array_t +rcutils_get_zero_initialized_uint8_array(void); + +/// Initialize a zero initialized uint8 array struct. +/** + * This function may leak if the uint8 array struct is already initialized. + * If the capacity is set to 0, no memory is allocated and the internal buffer + * is still NULL. + * + * \param uint8_array a pointer to the to be initialized uint8 array struct + * \param buffer_capacity the size of the memory to allocate for the byte stream + * \param allocator the allocator to use for the memory allocation + * \return `RCUTILS_RET_OK` if successful, or + * \return `RCUTILS_RET_INVALID_ARGUMENTS` if any arguments are invalid, or + * \return 'RCUTILS_RET_BAD_ALLOC` if no memory could be allocated correctly + * \return `RCUTILS_RET_ERROR` if an unexpected error occurs + */ +RCUTILS_PUBLIC +RCUTILS_WARN_UNUSED +rcutils_ret_t +rcutils_uint8_array_init( + rcutils_uint8_array_t * uint8_array, + size_t buffer_capacity, + const rcutils_allocator_t * allocator); + +/// Finalize a uint8 array struct. +/** + * Cleans up and deallocates any resources used in a rcutils_uint8_array_t. + * The array passed to this function needs to have been initialized by + * rcutils_uint8_array_init(). + * Passing an uninitialized instance to this function leads to undefined + * behavior. + * + * \param uint8_array pointer to the rcutils_uint8_array_t to be cleaned up + * \return `RCUTILS_RET_OK` if successful, or + * \return `RCUTILS_RET_INVALID_ARGUMENTS` if the uint8_array argument is invalid + * \return `RCUTILS_RET_ERROR` if an unexpected error occurs + */ +RCUTILS_PUBLIC +RCUTILS_WARN_UNUSED +rcutils_ret_t +rcutils_uint8_array_fini(rcutils_uint8_array_t * uint8_array); + +/// Resize the internal buffer of the uint8 array. +/** + * The internal buffer of the uint8 array can be resized dynamically if needed. + * If the new size is smaller than the current capacity, then the memory is + * truncated. + * Be aware, that this might deallocate the memory and therefore invalidates any + * pointers to this storage. + * + * \param uint8_array pointer to the instance of rcutils_uint8_array_t which is being resized + * \param new_size the new size of the internal buffer + * \return `RCUTILS_RET_OK` if successful, or + * \return `RCUTILS_RET_INVALID_ARGUMENT` if new_size is set to zero + * \return `RCUTILS_RET_BAD_ALLOC` if memory allocation failed, or + * \return `RCUTILS_RET_ERROR` if an unexpected error occurs + */ +RCUTILS_PUBLIC +RCUTILS_WARN_UNUSED +rcutils_ret_t +rcutils_uint8_array_resize(rcutils_uint8_array_t * uint8_array, size_t new_size); + +#if __cplusplus +} +#endif + +#endif // RCUTILS__TYPES__UINT8_ARRAY_H_ diff --git a/src/uint8_array.c b/src/uint8_array.c new file mode 100644 index 00000000..85eeecf6 --- /dev/null +++ b/src/uint8_array.c @@ -0,0 +1,106 @@ +// Copyright 2018 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 "rcutils/error_handling.h" +#include "rcutils/types/uint8_array.h" + +rcutils_uint8_array_t +rcutils_get_zero_initialized_uint8_array(void) +{ + static rcutils_uint8_array_t uint8_array = { + .buffer = NULL, + .buffer_length = 0lu, + .buffer_capacity = 0lu + }; + uint8_array.allocator = rcutils_get_zero_initialized_allocator(); + return uint8_array; +} + +rcutils_ret_t +rcutils_uint8_array_init( + rcutils_uint8_array_t * uint8_array, + size_t buffer_capacity, + const rcutils_allocator_t * allocator) +{ + RCUTILS_CHECK_ARGUMENT_FOR_NULL(uint8_array, RCUTILS_RET_INVALID_ARGUMENT); + RCUTILS_CHECK_ALLOCATOR(allocator, return RCUTILS_RET_INVALID_ARGUMENT); + + uint8_array->buffer_length = 0lu; + uint8_array->buffer_capacity = buffer_capacity; + uint8_array->allocator = *allocator; + + if (buffer_capacity > 0lu) { + uint8_array->buffer = (uint8_t *)allocator->allocate( + buffer_capacity * sizeof(uint8_t), allocator->state); + RCUTILS_CHECK_FOR_NULL_WITH_MSG( + uint8_array->buffer, + "failed to allocate memory for uint8 array", + uint8_array->buffer_capacity = 0lu; + uint8_array->buffer_length = 0lu; + return RCUTILS_RET_BAD_ALLOC); + } + + return RCUTILS_RET_OK; +} + +rcutils_ret_t +rcutils_uint8_array_fini(rcutils_uint8_array_t * uint8_array) +{ + RCUTILS_CHECK_ARGUMENT_FOR_NULL(uint8_array, RCUTILS_RET_INVALID_ARGUMENT); + + rcutils_allocator_t * allocator = &uint8_array->allocator; + RCUTILS_CHECK_ALLOCATOR(allocator, return RCUTILS_RET_INVALID_ARGUMENT); + + allocator->deallocate(uint8_array->buffer, allocator->state); + uint8_array->buffer = NULL; + uint8_array->buffer_length = 0lu; + uint8_array->buffer_capacity = 0lu; + + return RCUTILS_RET_OK; +} + +rcutils_ret_t +rcutils_uint8_array_resize(rcutils_uint8_array_t * uint8_array, size_t new_size) +{ + RCUTILS_CHECK_ARGUMENT_FOR_NULL(uint8_array, RCUTILS_RET_INVALID_ARGUMENT); + + if (new_size == 0lu) { + RCUTILS_SET_ERROR_MSG("new size of uint8_array has to be greater than zero"); + return RCUTILS_RET_INVALID_ARGUMENT; + } + + rcutils_allocator_t * allocator = &uint8_array->allocator; + RCUTILS_CHECK_ALLOCATOR(allocator, return RCUTILS_RET_INVALID_ARGUMENT); + + if (new_size == uint8_array->buffer_capacity) { + // nothing to do here + return RCUTILS_RET_OK; + } + + uint8_array->buffer = rcutils_reallocf( + uint8_array->buffer, new_size * sizeof(uint8_t), allocator); + RCUTILS_CHECK_FOR_NULL_WITH_MSG( + uint8_array->buffer, + "failed to reallocate memory for uint8 array", + uint8_array->buffer_capacity = 0lu; + uint8_array->buffer_length = 0lu; + return RCUTILS_RET_BAD_ALLOC); + + uint8_array->buffer_capacity = new_size; + if (new_size < uint8_array->buffer_length) { + uint8_array->buffer_length = new_size; + } + + return RCUTILS_RET_OK; +} diff --git a/test/test_uint8_array.cpp b/test/test_uint8_array.cpp new file mode 100644 index 00000000..748648ee --- /dev/null +++ b/test/test_uint8_array.cpp @@ -0,0 +1,78 @@ +// Copyright 2018 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 "rcutils/allocator.h" + +#include "rcutils/types/uint8_array.h" + +TEST(test_uint8_array, default_initialization) { + auto uint8_array = rcutils_get_zero_initialized_uint8_array(); + + auto allocator = rcutils_get_default_allocator(); + EXPECT_EQ(RCUTILS_RET_OK, rcutils_uint8_array_init(&uint8_array, 0, &allocator)); + EXPECT_EQ(0lu, uint8_array.buffer_capacity); + EXPECT_EQ(RCUTILS_RET_OK, rcutils_uint8_array_fini(&uint8_array)); + EXPECT_EQ(0lu, uint8_array.buffer_capacity); + EXPECT_FALSE(uint8_array.buffer); +} + +TEST(test_uint8_array, resize) { + auto uint8_array = rcutils_get_zero_initialized_uint8_array(); + auto allocator = rcutils_get_default_allocator(); + auto ret = rcutils_uint8_array_init(&uint8_array, 5, &allocator); + ASSERT_EQ(RCUTILS_RET_OK, ret); + + for (size_t i = 0; i < 5; ++i) { + uint8_t c = 0xFF; + memcpy(uint8_array.buffer + i, &c, 1); + } + uint8_array.buffer_length = 5; + for (size_t i = 0; i < uint8_array.buffer_length; ++i) { + EXPECT_EQ(0xFF, uint8_array.buffer[i]); + } + + ret = rcutils_uint8_array_resize(&uint8_array, 0); + ASSERT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret); + EXPECT_EQ(5lu, uint8_array.buffer_capacity); + EXPECT_EQ(5lu, uint8_array.buffer_length); + + ret = rcutils_uint8_array_resize(&uint8_array, 10); + ASSERT_EQ(RCUTILS_RET_OK, ret); + EXPECT_EQ(10u, uint8_array.buffer_capacity); + EXPECT_EQ(5u, uint8_array.buffer_length); + + for (uint8_t i = 0; i < 10; ++i) { + uint8_t u = 0xFF - i; + memcpy(uint8_array.buffer + i, &u, 1); + } + uint8_array.buffer_length = 10lu; + for (size_t i = 0; i < uint8_array.buffer_length; ++i) { + uint8_t u = 0xFF - static_cast(i); + EXPECT_EQ(u, uint8_array.buffer[i]); + } + + ret = rcutils_uint8_array_resize(&uint8_array, 3); + ASSERT_EQ(RCUTILS_RET_OK, ret); + EXPECT_EQ(3lu, uint8_array.buffer_capacity); + EXPECT_EQ(3lu, uint8_array.buffer_length); + EXPECT_EQ(0xFF, uint8_array.buffer[0]); + EXPECT_EQ(0xFF - 1, uint8_array.buffer[1]); + EXPECT_EQ(0xFF - 2, uint8_array.buffer[2]); + // the other fields are garbage. + + // cleanup only 3 fields + EXPECT_EQ(RCUTILS_RET_OK, rcutils_uint8_array_fini(&uint8_array)); +}