From 2fb1756d61fbede78c1503f8a17a1e846b2eee56 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Wed, 10 Nov 2021 09:28:01 -0800 Subject: [PATCH] [common] adding `HeapData` class (#7129) This commit adds new class `HeapData` which represents a heap allocated data buffer of a given variable length. This class provides methods to set/copy the `HeapData` from/to a buffer or a `Message. This commit also adds unit test for the new class. --- Android.mk | 1 + src/core/BUILD.gn | 2 + src/core/CMakeLists.txt | 1 + src/core/Makefile.am | 2 + src/core/common/heap_data.cpp | 111 ++++++++++++++++++++ src/core/common/heap_data.hpp | 176 ++++++++++++++++++++++++++++++++ tests/unit/test_heap_string.cpp | 147 ++++++++++++++++++++++++++ 7 files changed, 440 insertions(+) create mode 100644 src/core/common/heap_data.cpp create mode 100644 src/core/common/heap_data.hpp diff --git a/Android.mk b/Android.mk index decf9095d..86afc6e66 100644 --- a/Android.mk +++ b/Android.mk @@ -221,6 +221,7 @@ LOCAL_SRC_FILES := \ src/core/coap/coap_secure.cpp \ src/core/common/crc16.cpp \ src/core/common/error.cpp \ + src/core/common/heap_data.cpp \ src/core/common/heap_string.cpp \ src/core/common/instance.cpp \ src/core/common/logging.cpp \ diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index 0a4423287..e415d65c5 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -387,6 +387,8 @@ openthread_core_files = [ "common/error.cpp", "common/error.hpp", "common/extension.hpp", + "common/heap_data.cpp", + "common/heap_data.hpp", "common/heap_string.cpp", "common/heap_string.hpp", "common/instance.cpp", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 6f0b1f1f6..d4b0140ee 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -94,6 +94,7 @@ set(COMMON_SOURCES coap/coap_secure.cpp common/crc16.cpp common/error.cpp + common/heap_data.cpp common/heap_string.cpp common/instance.cpp common/logging.cpp diff --git a/src/core/Makefile.am b/src/core/Makefile.am index 0cff05213..bf2feeee9 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -184,6 +184,7 @@ SOURCES_COMMON = \ coap/coap_secure.cpp \ common/crc16.cpp \ common/error.cpp \ + common/heap_data.cpp \ common/heap_string.cpp \ common/instance.cpp \ common/logging.cpp \ @@ -426,6 +427,7 @@ HEADERS_COMMON = \ common/equatable.hpp \ common/error.hpp \ common/extension.hpp \ + common/heap_data.hpp \ common/heap_string.hpp \ common/instance.hpp \ common/iterator_utils.hpp \ diff --git a/src/core/common/heap_data.cpp b/src/core/common/heap_data.cpp new file mode 100644 index 000000000..287c84039 --- /dev/null +++ b/src/core/common/heap_data.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2021, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file implements the `HeapData` (a heap allocated data). + */ + +#include "heap_data.hpp" + +#include "common/code_utils.hpp" +#include "common/debug.hpp" +#include "common/instance.hpp" + +namespace ot { + +Error HeapData::SetFrom(const uint8_t *aBuffer, uint16_t aLength) +{ + Error error; + + SuccessOrExit(error = UpdateBuffer(aLength)); + VerifyOrExit(aLength != 0); + + SuccessOrAssert(mData.CopyBytesFrom(aBuffer, aLength)); + +exit: + return error; +} + +Error HeapData::SetFrom(const Message &aMessage) +{ + Error error; + uint16_t length = aMessage.GetLength() - aMessage.GetOffset(); + + SuccessOrExit(error = UpdateBuffer(length)); + VerifyOrExit(length != 0); + + SuccessOrAssert(aMessage.Read(aMessage.GetOffset(), mData.GetBytes(), mData.GetLength())); + +exit: + return error; +} + +void HeapData::SetFrom(HeapData &&aHeapData) +{ + Free(); + TakeFrom(aHeapData); +} + +void HeapData::Free(void) +{ + Instance::HeapFree(mData.GetBytes()); + mData.Init(nullptr, 0); +} + +Error HeapData::UpdateBuffer(uint16_t aNewLength) +{ + Error error = kErrorNone; + + VerifyOrExit(aNewLength != mData.GetLength()); + + Instance::HeapFree(mData.GetBytes()); + + if (aNewLength == 0) + { + mData.Init(nullptr, 0); + } + else + { + uint8_t *newBuffer = static_cast(Instance::HeapCAlloc(aNewLength, sizeof(uint8_t))); + + VerifyOrExit(newBuffer != nullptr, error = kErrorNoBufs); + mData.Init(newBuffer, aNewLength); + } + +exit: + return error; +} + +void HeapData::TakeFrom(HeapData &aHeapData) +{ + mData.Init(aHeapData.mData.GetBytes(), aHeapData.GetLength()); + aHeapData.mData.Init(nullptr, 0); +} + +} // namespace ot diff --git a/src/core/common/heap_data.hpp b/src/core/common/heap_data.hpp new file mode 100644 index 000000000..26440ed6b --- /dev/null +++ b/src/core/common/heap_data.hpp @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2021, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file includes definitions for `HeapData` (heap allocated data). + */ + +#ifndef HEAP_DATA_HPP_ +#define HEAP_DATA_HPP_ + +#include "openthread-core-config.h" + +#include "common/data.hpp" +#include "common/message.hpp" + +namespace ot { + +/** + * This class represents a heap allocated data. + * + */ +class HeapData +{ +public: + /** + * This constructor initializes the `HeapData` as empty. + * + */ + HeapData(void) { mData.Init(nullptr, 0); } + + /** + * This is the move constructor for `HeapData`. + * + * `HeapData` is non-copyable (copy constructor is deleted) but move constructor is provided to allow it to to be + * used as return type (return by value) from functions/methods (which will then use move semantics). + * + * @param[in] aHeapData An rvalue reference to another `HeapData` to move from. + * + */ + HeapData(HeapData &&aHeapData) { TakeFrom(aHeapData); } + + /** + * This is the destructor for `HeapData` object. + * + */ + ~HeapData(void) { Free(); } + + /** + * This method indicates whether or not the `HeapData` is null (i.e., it was never successfully set or it was + * freed). + * + * @retval TRUE The `HeapData` is null. + * @retval FALSE The `HeapData` is not null. + * + */ + bool IsNull(void) const { return (mData.GetBytes() == nullptr); } + + /** + * This method returns a pointer to the `HeapData` bytes buffer. + * + * @returns A pointer to data buffer or `nullptr` if the `HeapData` is null (never set or freed). + * + */ + const uint8_t *GetBytes(void) const { return mData.GetBytes(); } + + /** + * This method returns the `HeapData` length. + * + * @returns The data length (number of bytes) or zero if the `HeadpData` is null. + * + */ + uint16_t GetLength(void) const { return mData.GetLength(); } + + /** + * This method sets the `HeapData` from the content of a given buffer. + * + * @param[in] aBuffer The buffer to copy bytes from. + * @param[in] aLength The buffer length (number of bytes). + * + * @retval kErrorNone Successfully set the `HeapData`. + * @retval kErrorNoBufs Failed to allocate buffer. + * + */ + Error SetFrom(const uint8_t *aBuffer, uint16_t aLength); + + /** + * This method sets the `HeapData` from the content of a given message. + * + * The bytes are copied from current offset in @p aMessage till the end of the message. + * + * @param[in] aMessage The message to copy bytes from (starting from offset till the end of message). + * @param[in] aLength The buffer length (number of bytes). + * + * @retval kErrorNone Successfully set the `HeapData`. + * @retval kErrorNoBufs Failed to allocate buffer. + * + */ + Error SetFrom(const Message &aMessage); + + /** + * This method sets the `HeapData` from another one (move semantics). + * + * @param[in] aHeapData The other `HeapData` to set from (rvalue reference). + * + */ + void SetFrom(HeapData &&aHeapData); + + /** + * This method appends the bytes from `HeapData` to a given message. + * + * @param[in] aMessage The message to append the bytes into. + * + * @retval kErrorNone Successfully copied the bytes from `HeapData` into @p aMessage. + * @retval kErrorNoBufs Failed to allocate buffer. + * + */ + Error CopyBytesTo(Message &aMessage) const { return aMessage.AppendBytes(mData.GetBytes(), mData.GetLength()); } + + /** + * This method copies the bytes from `HeapData` into a given buffer. + * + * It is up to the caller to ensure that @p aBuffer has enough space for the current data length. + * + * @param[in] aBuffer A pointer to buffer to copy the bytes into. + * + */ + void CopyBytesTo(uint8_t *aBuffer) const { return mData.CopyBytesTo(aBuffer); } + + /** + * This method frees any buffer allocated by the `HeapData`. + * + * The `HeapData` destructor will automatically call `Free()`. This method allows caller to free the buffer + * explicitly. + * + */ + void Free(void); + + HeapData(const HeapData &) = delete; + HeapData &operator=(const HeapData &) = delete; + +private: + Error UpdateBuffer(uint16_t aNewLength); + void TakeFrom(HeapData &aHeapData); + + MutableData mData; +}; + +} // namespace ot + +#endif // HEAP_DATA_HPP_ diff --git a/tests/unit/test_heap_string.cpp b/tests/unit/test_heap_string.cpp index b61c42444..d3e4c115f 100644 --- a/tests/unit/test_heap_string.cpp +++ b/tests/unit/test_heap_string.cpp @@ -34,6 +34,7 @@ #include "test_util.hpp" #include "common/code_utils.hpp" +#include "common/heap_data.hpp" #include "common/heap_string.hpp" namespace ot { @@ -87,6 +88,9 @@ void TestHeapString(void) HeapString str2; const char *oldBuffer; + printf("====================================================================================\n"); + printf("TestHeapString\n\n"); + printf("------------------------------------------------------------------------------------\n"); printf("After constructor\n\n"); VerifyString("str1", str1, nullptr); @@ -146,11 +150,154 @@ void TestHeapString(void) printf("\n -- PASS\n"); } +void PrintData(const HeapData &aData) +{ + DumpBuffer("data", aData.GetBytes(), aData.GetLength()); +} + +template void VerifyData(const HeapData &aData, const uint8_t (&aArray)[kLength]) +{ + VerifyData(aData, &aArray[0], kLength); +} + +static const uint8_t kTestValue = 0x77; + +// Function returning a `HeapData` by value. +HeapData GetData(void) +{ + HeapData data; + + SuccessOrQuit(data.SetFrom(&kTestValue, sizeof(kTestValue))); + + return data; +} + +void VerifyData(const HeapData &aData, const uint8_t *aBytes, uint16_t aLength) +{ + static constexpr uint16_t kMaxLength = 100; + uint8_t buffer[kMaxLength]; + + PrintData(aData); + + if (aLength == 0) + { + VerifyOrQuit(aData.IsNull()); + VerifyOrQuit(aData.GetBytes() == nullptr); + VerifyOrQuit(aData.GetLength() == 0); + } + else + { + VerifyOrQuit(!aData.IsNull()); + VerifyOrQuit(aData.GetBytes() != nullptr); + VerifyOrQuit(aData.GetLength() == aLength); + VerifyOrQuit(memcmp(aData.GetBytes(), aBytes, aLength) == 0, "Data content is incorrect"); + + aData.CopyBytesTo(buffer); + VerifyOrQuit(memcmp(buffer, aBytes, aLength) == 0, "CopyBytesTo() failed"); + } +} + +void TestHeapData(void) +{ + Instance * instance; + MessagePool * messagePool; + Message * message; + HeapData data; + const uint8_t *oldBuffer; + + static const uint8_t kData1[] = {10, 20, 3, 15, 100, 0, 60, 16}; + static const uint8_t kData2[] = "OpenThread HeapData"; + static const uint8_t kData3[] = {0xaa, 0xbb, 0xcc}; + static const uint8_t kData4[] = {0x11, 0x22, 0x33}; + + instance = static_cast(testInitInstance()); + VerifyOrQuit(instance != nullptr, "Null OpenThread instance"); + + messagePool = &instance->Get(); + VerifyOrQuit((message = messagePool->New(Message::kTypeIp6, 0)) != nullptr, "Message::New failed"); + + message->SetOffset(0); + + printf("\n\n====================================================================================\n"); + printf("TestHeapData\n\n"); + + printf("------------------------------------------------------------------------------------\n"); + printf("After constructor\n"); + VerifyData(data, nullptr, 0); + + printf("------------------------------------------------------------------------------------\n"); + printf("SetFrom(aBuffer, aLength)\n"); + + SuccessOrQuit(data.SetFrom(kData1, sizeof(kData1))); + VerifyData(data, kData1); + + SuccessOrQuit(data.SetFrom(kData2, sizeof(kData2))); + VerifyData(data, kData2); + + SuccessOrQuit(data.SetFrom(kData3, sizeof(kData3))); + VerifyData(data, kData3); + oldBuffer = data.GetBytes(); + + SuccessOrQuit(data.SetFrom(kData4, sizeof(kData4))); + VerifyData(data, kData4); + VerifyOrQuit(oldBuffer == data.GetBytes(), "did not reuse old buffer on same data length"); + + SuccessOrQuit(data.SetFrom(kData4, 0)); + VerifyData(data, nullptr, 0); + + printf("------------------------------------------------------------------------------------\n"); + printf("SetFrom(aMessage)\n"); + + SuccessOrQuit(message->Append(kData2)); + SuccessOrQuit(data.SetFrom(*message)); + VerifyData(data, kData2); + + SuccessOrQuit(message->Append(kData3)); + SuccessOrQuit(data.SetFrom(*message)); + PrintData(data); + VerifyOrQuit(data.GetLength() == message->GetLength()); + + message->SetOffset(sizeof(kData2)); + SuccessOrQuit(data.SetFrom(*message)); + VerifyData(data, kData3); + + printf("------------------------------------------------------------------------------------\n"); + printf("Free()\n"); + + data.Free(); + VerifyData(data, nullptr, 0); + + data.Free(); + VerifyData(data, nullptr, 0); + + printf("------------------------------------------------------------------------------------\n"); + printf("CopyBytesTo(aMessage)\n"); + + SuccessOrQuit(message->SetLength(0)); + + SuccessOrQuit(data.CopyBytesTo(*message)); + VerifyOrQuit(message->GetLength() == 0, "CopyBytesTo() failed"); + + SuccessOrQuit(data.SetFrom(kData1, sizeof(kData1))); + VerifyData(data, kData1); + SuccessOrQuit(data.CopyBytesTo(*message)); + VerifyOrQuit(message->GetLength() == data.GetLength(), "CopyBytesTo() failed"); + VerifyOrQuit(message->Compare(0, kData1), "CopyBytesTo() failed"); + + printf("------------------------------------------------------------------------------------\n"); + printf("SetFrom() move semantics\n\n"); + data.SetFrom(GetData()); + VerifyData(data, &kTestValue, sizeof(kTestValue)); + + printf("\n -- PASS\n"); +} + } // namespace ot int main(void) { ot::TestHeapString(); + ot::TestHeapData(); printf("\nAll tests passed.\n"); return 0; }