Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add rcutils_unsigned_char_array_t #125

Merged
merged 8 commits into from
Nov 21, 2018
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions include/rcutils/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
109 changes: 109 additions & 0 deletions include/rcutils/types/uint8_array.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// 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 <stdint.h>

#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
* pre-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_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.
* Passing a rcutils_uint8_array_t which has not been zero initialized using
* rcutils_get_zero_initialized_uint8_array() to this function is 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_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_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 will deallocate the memory and therefore invalidates any
* pointers to this storage.
* If the new size is larger, new memory is getting allocated and the existing
* content is copied over.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you using realloc internally? And if so do you also use it when shrinking the data? And if so does that not also potentially mean the pointer address can change?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see now that you are, so this applies I believe:

the original pointer ptr is invalidated and any access to it is undefined behavior (even if reallocation was in-place).
-- https://en.cppreference.com/w/cpp/memory/c/realloc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, the pointer might change. Is there any way around that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any way around that?

In which case? In general, no I don't think so.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have it documented just before this sentence that all pointers to the underlying buffer will be invalidated. What do you advise to do here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It says that will only happen "if the new size is larger", which is not the only case and the reason I brought it up initially.

At least it should be stated that you should always assume the pointer is invalid after calling this, regardless of the input arguments and how they related to current values.

*
* \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_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_
116 changes: 116 additions & 0 deletions src/uint8_array.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// 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 = 0u,
.buffer_capacity = 0u
};
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_FOR_NULL_WITH_MSG(
uint8_array,
"uint8 array pointer is null",
return RCUTILS_RET_ERROR);

if (!rcutils_allocator_is_valid(allocator)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RCUTILS_CHECK_ALLOCATOR(allocator, return RCUTILS_RET_INVALID_ARGUMENT);

Same for other occurrences below.

RCUTILS_SET_ERROR_MSG("uint8 array has no valid allocator");
return RCUTILS_RET_ERROR;
}

uint8_array->buffer_length = 0;
uint8_array->buffer_capacity = buffer_capacity;
uint8_array->allocator = *allocator;

if (buffer_capacity > 0u) {
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",
return RCUTILS_RET_BAD_ALLOC);
}

return RCUTILS_RET_OK;
}

rcutils_ret_t
rcutils_uint8_array_fini(rcutils_uint8_array_t * uint8_array)
{
RCUTILS_CHECK_FOR_NULL_WITH_MSG(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RCUTILS_CHECK_ARGUMENT_FOR_NULL(uint8_array, RCUTILS_RET_INVALID_ARGUMENT);

Same for other occurrences below.

uint8_array,
"uint8 array pointer is null",
return RCUTILS_RET_ERROR);

rcutils_allocator_t * allocator = &uint8_array->allocator;
if (!rcutils_allocator_is_valid(allocator)) {
RCUTILS_SET_ERROR_MSG("uint8 array has no valid allocator");
return RCUTILS_RET_ERROR;
}

allocator->deallocate(uint8_array->buffer, allocator->state);
uint8_array->buffer = NULL;
uint8_array->buffer_length = 0u;
uint8_array->buffer_capacity = 0u;

return RCUTILS_RET_OK;
}

rcutils_ret_t
rcutils_uint8_array_resize(rcutils_uint8_array_t * uint8_array, size_t new_size)
{
RCUTILS_CHECK_FOR_NULL_WITH_MSG(
uint8_array,
"uint8 array pointer is null",
return RCUTILS_RET_ERROR);

rcutils_allocator_t * allocator = &uint8_array->allocator;
if (!rcutils_allocator_is_valid(allocator)) {
RCUTILS_SET_ERROR_MSG("uint8 array has no valid allocator");
return RCUTILS_RET_ERROR;
}

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",
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;
}
73 changes: 73 additions & 0 deletions test/test_uint8_array.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// 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 <gtest/gtest.h>

#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(0u, uint8_array.buffer_capacity);
EXPECT_EQ(RCUTILS_RET_OK, rcutils_uint8_array_fini(&uint8_array));
EXPECT_EQ(0u, 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, 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 = 10;
for (size_t i = 0; i < uint8_array.buffer_length; ++i) {
uint8_t u = 0xFF - static_cast<uint8_t>(i);
EXPECT_EQ(u, uint8_array.buffer[i]);
}

ret = rcutils_uint8_array_resize(&uint8_array, 3);
ASSERT_EQ(RCUTILS_RET_OK, ret);
EXPECT_EQ(3u, uint8_array.buffer_capacity);
EXPECT_EQ(3u, 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));
}