From 2b2b3904f0b7174ff225d59d8f26e73271745c17 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Mon, 14 Jun 2021 22:48:43 -0700 Subject: [PATCH] [common] adding `HeapString` class (heap allocated string) (#6732) This commit adds a new class `HeapString` as heap allocated string. The buffer to store the string is allocated from heap and is manged by the `HeapString` class itself, e.g., it may be reused and/or freed and reallocated when the string is set. The `HeapString` destructor will always free the allocated buffer. This commit also adds a unit test `test_heap_string` 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_string.cpp | 98 +++++++++++++++++ src/core/common/heap_string.hpp | 185 ++++++++++++++++++++++++++++++++ tests/unit/CMakeLists.txt | 21 ++++ tests/unit/Makefile.am | 4 + tests/unit/test_heap_string.cpp | 156 +++++++++++++++++++++++++++ 9 files changed, 470 insertions(+) create mode 100644 src/core/common/heap_string.cpp create mode 100644 src/core/common/heap_string.hpp create mode 100644 tests/unit/test_heap_string.cpp diff --git a/Android.mk b/Android.mk index 2069b4cd4..660f20b83 100644 --- a/Android.mk +++ b/Android.mk @@ -218,6 +218,7 @@ LOCAL_SRC_FILES := \ src/core/coap/coap_secure.cpp \ src/core/common/crc16.cpp \ src/core/common/error.cpp \ + src/core/common/heap_string.cpp \ src/core/common/instance.cpp \ src/core/common/logging.cpp \ src/core/common/message.cpp \ diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index b3c1810f6..734842250 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -372,6 +372,8 @@ openthread_core_files = [ "common/error.cpp", "common/error.hpp", "common/extension.hpp", + "common/heap_string.cpp", + "common/heap_string.hpp", "common/instance.cpp", "common/instance.hpp", "common/iterator_utils.hpp", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index ede7ea51b..429056c6f 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -91,6 +91,7 @@ set(COMMON_SOURCES coap/coap_secure.cpp common/crc16.cpp common/error.cpp + common/heap_string.cpp common/instance.cpp common/logging.cpp common/message.cpp diff --git a/src/core/Makefile.am b/src/core/Makefile.am index f50cf6bf0..9d52cb15d 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -168,6 +168,7 @@ SOURCES_COMMON = \ coap/coap_secure.cpp \ common/crc16.cpp \ common/error.cpp \ + common/heap_string.cpp \ common/instance.cpp \ common/logging.cpp \ common/message.cpp \ @@ -384,6 +385,7 @@ HEADERS_COMMON = \ common/equatable.hpp \ common/error.hpp \ common/extension.hpp \ + common/heap_string.hpp \ common/instance.hpp \ common/iterator_utils.hpp \ common/linked_list.hpp \ diff --git a/src/core/common/heap_string.cpp b/src/core/common/heap_string.cpp new file mode 100644 index 000000000..4806f61d4 --- /dev/null +++ b/src/core/common/heap_string.cpp @@ -0,0 +1,98 @@ +/* + * 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 `HeapString` (a heap allocated string). + */ + +#include "heap_string.hpp" + +#include "common/code_utils.hpp" +#include "common/instance.hpp" +#include "common/string.hpp" + +namespace ot { + +Error HeapString::Set(const char *aCString) +{ + Error error = kErrorNone; + size_t curSize; + size_t newSize; + + VerifyOrExit(aCString != nullptr, Free()); + + curSize = (mStringBuffer != nullptr) ? strlen(mStringBuffer) + 1 : 0; + newSize = strlen(aCString) + 1; + + if (curSize != newSize) + { + char *newBuffer = static_cast(Instance::HeapCAlloc(sizeof(char), newSize)); + + VerifyOrExit(newBuffer != nullptr, error = kErrorNoBufs); + + Instance::HeapFree(mStringBuffer); + mStringBuffer = newBuffer; + } + + memcpy(mStringBuffer, aCString, newSize); + +exit: + return error; +} + +Error HeapString::Set(HeapString &&aString) +{ + VerifyOrExit(mStringBuffer != aString.mStringBuffer); + + Instance::HeapFree(mStringBuffer); + mStringBuffer = aString.mStringBuffer; + aString.mStringBuffer = nullptr; + +exit: + return kErrorNone; +} + +void HeapString::Free(void) +{ + Instance::HeapFree(mStringBuffer); + mStringBuffer = nullptr; +} + +bool HeapString::operator==(const char *aCString) const +{ + bool isEqual; + + VerifyOrExit((aCString != nullptr) && (mStringBuffer != nullptr), isEqual = (mStringBuffer == aCString)); + isEqual = (strcmp(mStringBuffer, aCString) == 0); + +exit: + return isEqual; +} + +} // namespace ot diff --git a/src/core/common/heap_string.hpp b/src/core/common/heap_string.hpp new file mode 100644 index 000000000..7d1ecfae8 --- /dev/null +++ b/src/core/common/heap_string.hpp @@ -0,0 +1,185 @@ +/* + * 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 `HeapString` (a heap allocated string). + */ + +#ifndef HEAP_STRING_HPP_ +#define HEAP_STRING_HPP_ + +#include "openthread-core-config.h" + +#include "common/equatable.hpp" +#include "common/error.hpp" + +namespace ot { + +/** + * This class represents a heap allocated string. + * + * The buffer to store the string is allocated from heap and is manged by the `HeapString` class itself, e.g., it may + * be reused and/or freed and reallocated when the string is set. The `HeapString` destructor will always free the + * allocated buffer. + * + */ +class HeapString : public Unequatable +{ +public: + /** + * This constructor initializes the `HeapString` as null (or empty). + * + */ + HeapString(void) + : mStringBuffer(nullptr) + { + } + + /** + * This is the move constructor for `HeapString`. + * + * `HeapString` 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). + * + */ + HeapString(HeapString &&aString) + : mStringBuffer(aString.mStringBuffer) + { + aString.mStringBuffer = nullptr; + } + + /** + * This is the destructor for `HealString` object + * + */ + ~HeapString(void) { Free(); } + + /** + * This method indicates whether or not the `HeapString` is null (i.e., it was never successfully set or it was + * freed). + * + * @retval TRUE The `HeapString` is null. + * @retval FALSE The `HeapString` is not null. + * + */ + bool IsNull(void) const { return (mStringBuffer == nullptr); } + + /** + * This method returns the `HeapString` as a C string. + * + * @returns A pointer to C string buffer or `nullptr` if the `HeapString` is null (never set or freed). + * + */ + const char *AsCString(void) const { return mStringBuffer; } + + /** + * This method sets the string from a given C string. + * + * @param[in] aCString A pointer to c string buffer. Can be `nullptr` which then frees the `HeapString`. + * + * @retval kErrorNone Successfully set the string. + * @retval kErrorNoBufs Failed to allocate buffer for string. + * + */ + Error Set(const char *aCString); + + /** + * This method sets the string from another `HeapString`. + * + * @param[in] aString The other `HeapString` to set from. + * + * @retval kErrorNone Successfully set the string. + * @retval kErrorNoBufs Failed to allocate buffer for string. + * + */ + Error Set(const HeapString &aString) { return Set(aString.AsCString()); } + + /** + * This method sets the string from another `HeapString`. + * + * @param[in] aString The other `HeapString` to set from (rvalue reference using move semantics). + * + * @retval kErrorNone Successfully set the string. + * @retval kErrorNoBufs Failed to allocate buffer for string. + * + */ + Error Set(HeapString &&aString); + + /** + * This method frees any buffer allocated by the `HeapString`. + * + * The `HeapString` destructor will automatically call `Free()`. This method allows caller to free buffer + * explicitly. + * + */ + void Free(void); + + /** + * This method overloads operator `==` to evaluate whether or not the `HeapString` is equal to a given C string. + * + * @param[in] aCString A C string to compare with. Can be `nullptr` which then checks if `HeapString` is null. + * + * @retval TRUE If the two strings are equal. + * @retval FALSE If the two strings are not equal. + * + */ + bool operator==(const char *aCString) const; + + /** + * This method overloads operator `!=` to evaluate whether or not the `HeapString` is unequal to a given C string. + * + * @param[in] aCString A C string to compare with. Can be `nullptr` which then checks if `HeapString` is not null. + * + * @retval TRUE If the two strings are not equal. + * @retval FALSE If the two strings are equal. + * + */ + bool operator!=(const char *aCString) const { return !(*this == aCString); } + + /** + * This method overloads operator `==` to evaluate whether or not two `HeapString` are equal. + * + * @param[in] aString The other string to compare with. + * + * @retval TRUE If the two strings are equal. + * @retval FALSE If the two strings are not equal. + * + */ + bool operator==(const HeapString &aString) const { return (*this == aString.AsCString()); } + + HeapString(const HeapString &) = delete; + HeapString &operator=(const HeapString &) = delete; + +private: + char *mStringBuffer; +}; + +} // namespace ot + +#endif // HEAP_STRING_HPP_ diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 5b6a24551..69d9810ae 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -258,6 +258,27 @@ target_link_libraries(ot-test-heap add_test(NAME ot-test-heap COMMAND ot-test-heap) +add_executable(ot-test-heap-string + test_heap_string.cpp +) + +target_include_directories(ot-test-heap-string + PRIVATE + ${COMMON_INCLUDES} +) + +target_compile_options(ot-test-heap-string + PRIVATE + ${COMMON_COMPILE_OPTIONS} +) + +target_link_libraries(ot-test-heap-string + PRIVATE + ${COMMON_LIBS} +) + +add_test(NAME ot-test-heap-string COMMAND ot-test-heap-string) + add_executable(ot-test-hkdf-sha256 ${COMMON_SOURCES} test_hkdf_sha256.cpp diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am index 132e5f5bd..55a699763 100644 --- a/tests/unit/Makefile.am +++ b/tests/unit/Makefile.am @@ -115,6 +115,7 @@ check_PROGRAMS += \ ot-test-ecdsa \ ot-test-flash \ ot-test-heap \ + ot-test-heap-string \ ot-test-hkdf-sha256 \ ot-test-hmac-sha256 \ ot-test-ip-address \ @@ -206,6 +207,9 @@ ot_test_hdlc_SOURCES = $(COMMON_SOURCES) test_hdlc.cpp ot_test_heap_LDADD = $(COMMON_LDADD) ot_test_heap_SOURCES = $(COMMON_SOURCES) test_heap.cpp +ot_test_heap_string_LDADD = $(COMMON_LDADD) +ot_test_heap_string_SOURCES = $(COMMON_SOURCES) test_heap_string.cpp + ot_test_hkdf_sha256_LDADD = $(COMMON_LDADD) ot_test_hkdf_sha256_SOURCES = $(COMMON_SOURCES) test_hkdf_sha256.cpp diff --git a/tests/unit/test_heap_string.cpp b/tests/unit/test_heap_string.cpp new file mode 100644 index 000000000..482ce13f5 --- /dev/null +++ b/tests/unit/test_heap_string.cpp @@ -0,0 +1,156 @@ +/* + * 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. + */ + +#include "test_platform.h" + +#include + +#include + +#include "test_util.hpp" +#include "common/code_utils.hpp" +#include "common/heap_string.hpp" + +namespace ot { + +void PrintString(const char *aName, const HeapString &aString) +{ + if (aString.IsNull()) + { + printf("%s = (null)\n", aName); + } + else + { + printf("%s = [%zu] \"%s\"\n", aName, strlen(aString.AsCString()), aString.AsCString()); + } +} + +void VerifyString(const char *aName, const HeapString &aString, const char *aExpectedString) +{ + PrintString(aName, aString); + + if (aExpectedString == nullptr) + { + VerifyOrQuit(aString.IsNull(), "IsNull() is incorrect"); + VerifyOrQuit(aString.AsCString() == nullptr, "AsCString() is incorrect"); + VerifyOrQuit(aString != "something", "operator!=() failed"); + } + else + { + VerifyOrQuit(!aString.IsNull(), "IsNull() is incorrect"); + VerifyOrQuit(aString.AsCString() != nullptr, "AsCString() is incorrect"); + VerifyOrQuit(strcmp(aString.AsCString(), aExpectedString) == 0, "String content is incorrect"); + VerifyOrQuit(aString != nullptr, "operator!=() failed"); + } + + VerifyOrQuit(aString == aExpectedString, "operator==() failed"); +} + +// Function returning a `HeapString` by value. +HeapString GetName(void) +{ + HeapString name; + + SuccessOrQuit(name.Set("name"), "Set() failed"); + + return name; +} + +void TestHeapString(void) +{ + HeapString str1; + HeapString str2; + const char *oldBuffer; + + printf("------------------------------------------------------------------------------------\n"); + printf("After constructor\n\n"); + VerifyString("str1", str1, nullptr); + + printf("------------------------------------------------------------------------------------\n"); + printf("Set(const char *aCstring)\n\n"); + SuccessOrQuit(str1.Set("hello"), "Set() failed"); + VerifyString("str1", str1, "hello"); + oldBuffer = str1.AsCString(); + + SuccessOrQuit(str1.Set("0123456789"), "Set() failed"); + VerifyString("str1", str1, "0123456789"); + printf("\tDid reuse its old buffer: %s\n", str1.AsCString() == oldBuffer ? "yes" : "no"); + oldBuffer = str1.AsCString(); + + SuccessOrQuit(str1.Set("9876543210"), "Set() failed"); + VerifyString("str1", str1, "9876543210"); + printf("\tDid reuse its old buffer (same length): %s\n", str1.AsCString() == oldBuffer ? "yes" : "no"); + + printf("------------------------------------------------------------------------------------\n"); + printf("Set(const HeapString &)\n\n"); + SuccessOrQuit(str2.Set(str1), "Set() failed"); + VerifyString("str2", str2, str1.AsCString()); + + SuccessOrQuit(str1.Set(nullptr), "Set() failed"); + VerifyString("str1", str1, nullptr); + + SuccessOrQuit(str2.Set(str1), "Set() failed"); + VerifyString("str2", str2, nullptr); + + printf("------------------------------------------------------------------------------------\n"); + printf("Free()\n\n"); + str1.Free(); + VerifyString("str1", str1, nullptr); + + SuccessOrQuit(str1.Set("hello again"), "Set() failed"); + VerifyString("str1", str1, "hello again"); + + str1.Free(); + VerifyString("str1", str1, nullptr); + + printf("------------------------------------------------------------------------------------\n"); + printf("Set() move semantics\n\n"); + SuccessOrQuit(str1.Set("old name"), "Set() failed"); + PrintString("str1", str1); + SuccessOrQuit(str1.Set(GetName()), "Set() with move semantics failed"); + VerifyString("str1", str1, "name"); + + printf("------------------------------------------------------------------------------------\n"); + printf("operator==() with two null string\n\n"); + str1.Free(); + str2.Free(); + VerifyString("str1", str1, nullptr); + VerifyString("str2", str2, nullptr); + VerifyOrQuit(str1 == str2, "operator==() failed with two null strings"); + + printf("\n -- PASS\n"); +} + +} // namespace ot + +int main(void) +{ + ot::TestHeapString(); + printf("\nAll tests passed.\n"); + return 0; +}