diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index 2837bf2fa..8341b242c 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -366,6 +366,7 @@ openthread_core_files = [ "coap/coap_secure.cpp", "coap/coap_secure.hpp", "common/arg_macros.hpp", + "common/array.hpp", "common/bit_vector.hpp", "common/clearable.hpp", "common/code_utils.hpp", diff --git a/src/core/Makefile.am b/src/core/Makefile.am index b0f87b32f..48e757755 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -378,6 +378,7 @@ HEADERS_COMMON = \ coap/coap_message.hpp \ coap/coap_secure.hpp \ common/arg_macros.hpp \ + common/array.hpp \ common/bit_vector.hpp \ common/clearable.hpp \ common/code_utils.hpp \ diff --git a/src/core/common/array.hpp b/src/core/common/array.hpp new file mode 100644 index 000000000..b4c56b02b --- /dev/null +++ b/src/core/common/array.hpp @@ -0,0 +1,326 @@ +/* + * 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 a generic array. + */ + +#ifndef ARRAY_HPP_ +#define ARRAY_HPP_ + +#include "openthread-core-config.h" + +#include "common/code_utils.hpp" +#include "common/error.hpp" + +namespace ot { + +/** + * This template class represents an array of elements with a fixed max size. + * + * @tparam Type The array item type. + * @tparam kMaxSize Specifies the max array size (maximum number of elements in the array). + * + */ +template class Array +{ +public: + /** + * This constructor initializes the array as empty. + * + */ + Array(void) + : mLength(0) + { + } + + /** + * This constructor initializes the array by copying elements from another array. + * + * The method uses assignment `=` operator on `Type` to copy each element from @p aOtherArray into the elements of + * the array. + * + * @param[in] aOtherArray Another array to copy from. + * + */ + Array(const Array &aOtherArray) { *this = aOtherArray; } + + /** + * This method clears the array. + * + */ + void Clear(void) { mLength = 0; } + + /** + * This method indicates whether or not the array is empty. + * + * @retval TRUE when array is empty. + * @retval FALSE when array is not empty. + * + */ + bool IsEmpty(void) const { return (mLength == 0); } + + /** + * This method indicates whether or not the array is full. + * + * @retval TRUE when array is full. + * @retval FALSE when array is not full. + * + */ + bool IsFull(void) const { return (mLength == kMaxSize); } + + /** + * This method returns the maximum array size (max number of elements). + * + * @returns The maximum array size (max number of elements that can be added to the array). + * + */ + uint16_t GetMaxSize(void) const { return kMaxSize; } + + /** + * This method returns the current length of array (number of elements). + * + * @returns The current array length. + * + */ + uint16_t GetLength(void) const { return mLength; } + + /** + * This method overloads the `[]` operator to get the element at a given index. + * + * This method does not perform index bounds checking. Behavior is undefined if @p aIndex is not valid. + * + * @param[in] aIndex The index to get. + * + * @returns A reference to the element in array at @p aIndex. + * + */ + Type &operator[](uint16_t aIndex) { return mElements[aIndex]; } + + /** + * This method overloads the `[]` operator to get the element at a given index. + * + * This method does not perform index bounds checking. Behavior is undefined if @p aIndex is not valid. + * + * @param[in] aIndex The index to get. + * + * @returns A reference to the element in array at @p aIndex. + * + */ + const Type &operator[](uint16_t aIndex) const { return mElements[aIndex]; } + + /** + * This method gets a pointer to the element at a given index. + * + * Unlike `operator[]`, this method checks @p aIndex to be valid and within the current length. + * + * @param[in] aIndex The index to get. + * + * @returns A pointer to element in array at @p aIndex or `nullptr` if @p aIndex is not valid. + * + */ + Type *At(uint16_t aIndex) { return (aIndex < mLength) ? &mElements[aIndex] : nullptr; } + + /** + * This method gets a pointer to the element at a given index. + * + * Unlike `operator[]`, this method checks @p aIndex to be valid and within the current length. + * + * @param[in] aIndex The index to get. + * + * @returns A pointer to element in array at @p aIndex or `nullptr` if @p aIndex is not valid. + * + */ + const Type *At(uint16_t aIndex) const { return (aIndex < mLength) ? &mElements[aIndex] : nullptr; } + + /** + * This method gets a pointer to the element at the front of the array (first element). + * + * @returns A pointer to the front element or `nullptr` if array is empty. + * + */ + Type *Front(void) { return At(0); } + + /** + * This method gets a pointer to the element at the front of the array (first element). + * + * @returns A pointer to the front element or `nullptr` if array is empty. + * + */ + const Type *Front(void) const { return At(0); } + + /** + * This method gets a pointer to the element at the back of the array (last element). + * + * @returns A pointer to the back element or `nullptr` if array is empty. + * + */ + Type *Back(void) { return At(mLength - 1); } + + /** + * This method gets a pointer to the element at the back of the array (last element). + * + * @returns A pointer to the back element or `nullptr` if array is empty. + * + */ + const Type *Back(void) const { return At(mLength - 1); } + + /** + * This method appends a new entry to the end of the array. + * + * The method uses assignment `=` operator on `Type` to copy @p aEntry into the added array element. + * + * @param[in] aEntry The new entry to push back. + * + * @retval kErrorNone Successfully pushed back @p aEntry to the end of the array. + * @retval kErrorNoBufs Could not append the new element since array is full. + * + */ + Error PushBack(const Type &aEntry) { return IsFull() ? kErrorNoBufs : (mElements[mLength++] = aEntry, kErrorNone); } + + /** + * This method appends a new entry to the end of the array. + * + * On success, this method returns a pointer to the newly appended element in the array for the caller to + * initialize and use. + * + * @return A pointer to the newly appended element or `nullptr` if array is full. + * + */ + Type *PushBack(void) { return IsFull() ? nullptr : &mElements[mLength++]; } + + /** + * This method removes the last element in the array. + * + * @returns A pointer to the removed element from the array, or `nullptr` if array is empty. + * + */ + Type *PopBack(void) { return IsEmpty() ? nullptr : &mElements[--mLength]; } + + /** + * This method returns the index of an element in the array. + * + * The @p aElement MUST be from the array, otherwise the behavior of this method is undefined. + * + * @param[in] aElement A reference to an element in the array. + * + * @returns The index of @p aElement in the array. + * + */ + uint16_t IndexOf(const Type &aElement) const { return static_cast(&aElement - &mElements[0]); } + + /** + * This method finds the first match of a given entry in the array. + * + * This method uses `==` operator on `Type` to compare the array element with @p aEntry. + * + * @param[in] aEntry The entry to search for within the array. + * + * @returns A pointer to matched array element, or `nullptr` if a match could not be found. + * + */ + Type *Find(const Type &aEntry) { return const_cast(const_cast(this)->Find(aEntry)); } + + /** + * This method finds the first match of a given entry in the array. + * + * This method uses `==` operator to compare the array elements with @p aEntry. + * + * @param[in] aEntry The entry to search for within the array. + * + * @returns A pointer to matched array element, or `nullptr` if a match could not be found. + * + */ + const Type *Find(const Type &aEntry) const + { + const Type *matched = nullptr; + + for (const Type &element : *this) + { + if (element == aEntry) + { + matched = &element; + break; + } + } + + return matched; + } + + /** + * This method indicates whether or not a match to given entry exists in the array. + * + * This method uses `==` operator on `Type` to compare the array elements with @p aEntry. + * + * @param[in] aEntry The entry to search for within the array. + * + * @retval TRUE The array contains a matching element with @p aEntry. + * @retval FALSE The array does not contain a matching element with @p aEntry. + * + */ + bool Contains(const Type &aEntry) const { return Find(aEntry) != nullptr; } + + /** + * This method overloads assignment `=` operator to copy elements from another array into the array. + * + * The method uses assignment `=` operator on `Type` to copy each element from @p aOtherArray into the elements of + * the array. + * + * @param[in] aOtherArray Another array to copy from. + * + */ + Array &operator=(const Array &aOtherArray) + { + Clear(); + + for (const Type &otherElement : aOtherArray) + { + IgnoreError(PushBack(otherElement)); + } + + return *this; + } + + // The following methods are intended to support range-based `for` + // loop iteration over the array elements and should not be used + // directly. + + Type * begin(void) { return &mElements[0]; } + Type * end(void) { return &mElements[mLength]; } + const Type *begin(void) const { return &mElements[0]; } + const Type *end(void) const { return &mElements[mLength]; } + +private: + Type mElements[kMaxSize]; + uint16_t mLength; +}; + +} // namespace ot + +#endif // ARRAY_HPP_ diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 69d9810ae..6c41818c4 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -88,6 +88,25 @@ target_link_libraries(ot-test-aes add_test(NAME ot-test-aes COMMAND ot-test-aes) +add_executable(ot-test-array + test_array.cpp +) + +target_include_directories(ot-test-array + PRIVATE + ${COMMON_INCLUDES} +) + +target_compile_options(ot-test-array + PRIVATE + ${COMMON_COMPILE_OPTIONS} +) + +target_link_libraries(ot-test-array + PRIVATE + ${COMMON_LIBS} +) + add_executable(ot-test-child test_child.cpp ) diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am index 55a699763..62e85a496 100644 --- a/tests/unit/Makefile.am +++ b/tests/unit/Makefile.am @@ -107,6 +107,7 @@ check_PROGRAMS = \ if OPENTHREAD_ENABLE_FTD check_PROGRAMS += \ ot-test-aes \ + ot-test-array \ ot-test-checksum \ ot-test-child \ ot-test-child-table \ @@ -180,6 +181,9 @@ COMMON_SOURCES = test_platform.cpp test_util.cpp ot_test_aes_LDADD = $(COMMON_LDADD) ot_test_aes_SOURCES = $(COMMON_SOURCES) test_aes.cpp +ot_test_array_LDADD = $(COMMON_LDADD) +ot_test_array_SOURCES = $(COMMON_SOURCES) test_array.cpp + ot_test_checksum_LDADD = $(COMMON_LDADD) ot_test_checksum_SOURCES = $(COMMON_SOURCES) test_checksum.cpp diff --git a/tests/unit/test_array.cpp b/tests/unit/test_array.cpp new file mode 100644 index 000000000..3a93724b3 --- /dev/null +++ b/tests/unit/test_array.cpp @@ -0,0 +1,255 @@ +/* + * 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 +#include + +#include "test_platform.h" + +#include + +#include "common/array.hpp" +#include "common/debug.hpp" +#include "common/instance.hpp" + +#include "test_util.h" + +namespace ot { + +void TestArray(void) +{ + constexpr uint16_t kMaxSize = 10; + constexpr uint16_t kStartValue = 100; + + Array array; + uint16_t index; + uint16_t seed; + + // All methods after constructor + VerifyOrQuit(array.IsEmpty()); + VerifyOrQuit(!array.IsFull()); + VerifyOrQuit(array.GetLength() == 0); + VerifyOrQuit(array.GetMaxSize() == kMaxSize); + VerifyOrQuit(array.At(0) == nullptr); + VerifyOrQuit(array.Front() == nullptr); + VerifyOrQuit(array.Back() == nullptr); + VerifyOrQuit(array.PopBack() == nullptr); + + seed = kStartValue; + + for (uint16_t len = 1; len <= kMaxSize; len++) + { + for (uint8_t iter = 0; iter < 2; iter++) + { + // On `iter == 0` use `PushBack(aEntry)` and on `iter == 1` + // `PushBack()` version which returns a pointer to the newly + // added entry. + + if (iter == 0) + { + SuccessOrQuit(array.PushBack(seed + len)); + } + else + { + uint16_t *entry = array.PushBack(); + + VerifyOrQuit(entry != nullptr); + *entry = seed + len; + } + + VerifyOrQuit(!array.IsEmpty()); + VerifyOrQuit(array.IsFull() == (len == kMaxSize)); + VerifyOrQuit(array.GetLength() == len); + + VerifyOrQuit(array.Front() != nullptr); + VerifyOrQuit(*array.Front() == seed + 1); + VerifyOrQuit(array.Back() != nullptr); + VerifyOrQuit(*array.Back() == seed + len); + + for (index = 0; index < len; index++) + { + VerifyOrQuit(array[index] == seed + index + 1); + VerifyOrQuit(array.At(index) != nullptr); + VerifyOrQuit(*array.At(index) == seed + index + 1); + + VerifyOrQuit(array.Contains(seed + index + 1)); + VerifyOrQuit(array.Find(seed + index + 1) == &array[index]); + + VerifyOrQuit(!array.Contains(seed)); + VerifyOrQuit(array.Find(seed) == nullptr); + } + + index = 0; + + for (uint16_t value : array) + { + VerifyOrQuit(value == array[index]); + index++; + } + + index = 0; + + for (uint16_t &entry : array) + { + // Uddate the value stored at the entry + entry++; + + VerifyOrQuit(entry == array[index]); + VerifyOrQuit(array.IndexOf(entry) == index); + + index++; + } + + seed++; + + // On `iter == 0` we verify `PopBack()` and remove the + // last entry. It will be added again from next `iter` + // loop (on `iter == 1`). + + if (iter == 0) + { + uint16_t *entry = array.PopBack(); + + VerifyOrQuit(entry != nullptr); + VerifyOrQuit(*entry == seed + len); + VerifyOrQuit(array.GetLength() == len - 1); + } + } + } + + VerifyOrQuit(array.IsFull()); + VerifyOrQuit(array.PushBack(0) == kErrorNoBufs); + VerifyOrQuit(array.PushBack() == nullptr); + + for (uint16_t len = kMaxSize; len >= 1; len--) + { + uint16_t *entry; + + VerifyOrQuit(array.GetLength() == len); + VerifyOrQuit(array.Back() == &array[len - 1]); + + entry = array.PopBack(); + VerifyOrQuit(entry != nullptr); + VerifyOrQuit(*entry == seed + len); + + VerifyOrQuit(array.GetLength() == len - 1); + VerifyOrQuit(!array.IsFull()); + } + + VerifyOrQuit(array.IsEmpty()); + + SuccessOrQuit(array.PushBack(seed)); + VerifyOrQuit(!array.IsEmpty()); + + array.Clear(); + VerifyOrQuit(array.IsEmpty()); +} + +void TestArrayCopy(void) +{ + constexpr uint16_t kMaxSize = 10; + + struct Entry + { + Entry(void) = default; + + Entry(const char *aName, uint16_t aYear) + : mName(aName) + , mYear(aYear) + { + } + + bool operator==(const Entry &aOther) { return (mName == aOther.mName) && (mYear == aOther.mYear); } + + const char *mName; + uint16_t mYear; + }; + + Entry ps1("PS", 1994); + Entry ps2("PS2", 2000); + Entry ps3("PS3", 2006); + Entry ps4("PS4", 2013); + Entry ps5("PS5", 2020); + + Array array1; + Array array2; + Array array3(array1); + + VerifyOrQuit(array1.IsEmpty()); + VerifyOrQuit(array2.IsEmpty()); + VerifyOrQuit(array3.IsEmpty()); + + SuccessOrQuit(array1.PushBack(ps1)); + SuccessOrQuit(array1.PushBack(ps2)); + SuccessOrQuit(array1.PushBack(ps3)); + SuccessOrQuit(array1.PushBack(ps4)); + VerifyOrQuit(array1.GetLength() == 4); + + SuccessOrQuit(array2.PushBack(ps3)); + SuccessOrQuit(array2.PushBack(ps5)); + VerifyOrQuit(array2.GetLength() == 2); + + array3 = array2 = array1; + + VerifyOrQuit(array1.GetLength() == 4); + VerifyOrQuit(array2.GetLength() == 4); + VerifyOrQuit(array3.GetLength() == 4); + + for (uint8_t index = 0; index < array1.GetLength(); index++) + { + VerifyOrQuit(array1[index] == array2[index]); + VerifyOrQuit(array1[index] == array3[index]); + } + + array3.Clear(); + + array1 = array3; + VerifyOrQuit(array1.IsEmpty()); + VerifyOrQuit(array1.GetLength() == 0); + + { + Array array4(array2); + + VerifyOrQuit(array4.GetLength() == 4); + + for (uint8_t index = 0; index < array1.GetLength(); index++) + { + VerifyOrQuit(array2[index] == array4[index]); + } + } +} + +} // namespace ot + +int main(void) +{ + ot::TestArray(); + ot::TestArrayCopy(); + printf("All tests passed\n"); + return 0; +}