diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index bbc571e34..7dcc11883 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -375,6 +375,7 @@ openthread_core_files = [ "common/const_cast.hpp", "common/crc16.cpp", "common/crc16.hpp", + "common/data.hpp", "common/debug.hpp", "common/encoding.hpp", "common/equatable.hpp", diff --git a/src/core/Makefile.am b/src/core/Makefile.am index a517c4581..15b29d05f 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -394,6 +394,7 @@ HEADERS_COMMON = \ common/code_utils.hpp \ common/const_cast.hpp \ common/crc16.hpp \ + common/data.hpp \ common/debug.hpp \ common/encoding.hpp \ common/equatable.hpp \ diff --git a/src/core/common/data.hpp b/src/core/common/data.hpp new file mode 100644 index 000000000..1fb0078c2 --- /dev/null +++ b/src/core/common/data.hpp @@ -0,0 +1,325 @@ +/* + * 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 `Data` and `MutableData`. + */ + +#ifndef DATA_HPP_ +#define DATA_HPP_ + +#include "openthread-core-config.h" + +#include +#include + +#include "common/clearable.hpp" +#include "common/code_utils.hpp" +#include "common/const_cast.hpp" +#include "common/equatable.hpp" +#include "common/error.hpp" +#include "common/type_traits.hpp" + +namespace ot { + +/** + * This enumeration type is used as the template parameter in `Data` and `MutableData` to indicate the `uint` type to + * use for the data length. + * + */ +enum DataLengthType : uint8_t +{ + kWithUint8Length, ///< Use `uint8_t` for data length. + kWithUint16Length, ///< Use `uint16_t` for data length +}; + +template class MutableData; + +/** + * This type represents a generic `Data` which is simply a wrapper over a pointer to a buffer with a given data length. + * + * The data length can be either `uint8_t` or `uint16_t` (determined by the template parameter `kDataLengthType`). + * + * While a `Data` instance itself can change (for example, it can be updated to point to another buffer), it always + * treats the content of the buffer as immutable. + * + * A `Data` instance MUST be initialized (using any of the `Init()` methods) before calling any other methods on the + * instance (e.g., `GetBytes()` or `GetLength()`), otherwise the behavior is undefined. + * + * @tparam kDataLengthType Determines the data length type (`uint8_t` or `uint16_t`). + * + */ +template +class Data : public Clearable>, public Unequatable> +{ + friend class MutableData; + +public: + /** + * This type represents the data length type (`uint8_t` or `uint16_t`). + * + */ + using LengthType = typename TypeTraits::Conditional::Type; + + /** + * This method initializes the `Data` to point to a given buffer with a given length. + * + * @param[in] aBuffer A pointer to a buffer containing the data. + * @param[in] aLength The data length (number of bytes in @p aBuffer) + * + */ + void Init(const void *aBuffer, LengthType aLength) + { + mBuffer = static_cast(aBuffer); + mLength = aLength; + } + + /** + * This method initializes the `Data` to point to a range of bytes in a given buffer. + * + * The range is specified by the pointers to its start @p aStart and its end @p aEnd. `Data` will point to the + * bytes in the buffer from @p aStart up to but excluding @p aEnd (i.e., `aStart <= bytes < aEnd`). + * + * @param[in] aStart Pointer to the start of the range. + * @param[in] aEnd Pointer to the end of the range. + * + */ + void InitFromRange(const uint8_t *aStart, const uint8_t *aEnd) + { + Init(aStart, static_cast(aEnd - aStart)); + } + + /** + * This template method initializes the `Data` to point to the content of an object. + * + * @tparm ObjectType The object type (MUST not be a pointer type). + * + * @param[in] aObject The object to initialize the `Data` with. + * + */ + template void InitFrom(const ObjectType &aObject) + { + static_assert(!TypeTraits::IsPointer::kValue, "ObjectType MUST not be a pointer"); + Init(&aObject, sizeof(aObject)); + } + + /** + * This method returns a pointer to the data bytes buffer. + * + * @returns A pointer to the data bytes buffer (can be `nullptr` if `Data` is cleared). + * + */ + const uint8_t *GetBytes(void) const { return mBuffer; } + + /** + * This method returns the data length. + * + * @returns The data length (number of bytes). + * + */ + LengthType GetLength(void) const { return mLength; } + + /** + * This method copies the `Data` bytes to a given buffer. + * + * It is up to the caller to ensure that @p aBuffer has enough space for the current data length. + * + * @param[out] aBuffer The buffer to copy the bytes into. + * + */ + void CopyBytesTo(uint8_t *aBuffer) const { memcpy(aBuffer, mBuffer, mLength); } + + /** + * This method overloads operator `==` to compare the `Data` content with the content from another one. + * + * @param[in] aOtherData The other `Data` to compare with. + * + * @retval TRUE The two `Data` instances have matching content (same length and same bytes). + * @retval FALSE The two `Data` instances do not have matching content. + * + */ + bool operator==(const Data &aOtherData) const + { + return (mLength == aOtherData.mLength) && (memcmp(mBuffer, aOtherData.mBuffer, mLength) == 0); + } + + /** + * This method checks whether the `Data` starts with the same byte content as from another `Data` instance. + * + * This method checks that the `Data` instance contains the same bytes as @p aOtherData but allows it to have + * additional bytes at the end. + * + * @param[in] aOtherData The other `Data` to compare with. + * + * @retval TRUE This `Data` starts with the same byte content as in @p aOtherData. + * @retval FALSE This `Data` does not start with the same byte content as in @p aOtherData. + * + */ + bool StartsWith(const Data &aOtherData) const + { + return (mLength >= aOtherData.mLength) && (memcmp(mBuffer, aOtherData.mBuffer, aOtherData.mLength) == 0); + } + +private: + const uint8_t *mBuffer; + LengthType mLength; +}; + +/** + * This type represents a generic `MutableData` which is simply a wrapper over a pointer to a buffer with a given data + * length. + * + * It inherits from `Data` but unlike `Data` which treats its buffer content as immutable, `MutableData` allows its + * data buffer content to be changed. + * + * A `MutableData` instance MUST be initialized (using any of the `Init()` methods) before calling any other methods + * (e.g., `GetBytes()` or `GetLength()`), otherwise the behavior is undefined. + + * + */ +template class MutableData : public Data +{ + using Base = Data; + using Base::mBuffer; + using Base::mLength; + +public: + /** + * This type represents the data length type (`uint8_t` or `uint16_t`). + * + */ + using LengthType = typename Base::LengthType; + + /** + * This method initializes the `MutableData` to point to a given buffer with a given length. + * + * @param[in] aBuffer A pointer to a buffer containing the data. + * @param[in] aLength The data length (number of bytes in @p aBuffer) + * + */ + void Init(void *aBuffer, LengthType aLength) { Base::Init(aBuffer, aLength); } + + /** + * This method initializes the `MutableData` to point to a range of bytes in a given buffer. + * + * The range is specified by the pointers to its start @p aStart and its end @p aEnd. `Data` will point to the + * bytes in the buffer from @p aStart up to but excluding @p aEnd (i.e., `aStart <= bytes < aEnd`). + * + * @param[in] aStart Pointer to the start of the range. + * @param[in] aEnd Pointer to the end of the range. + * + */ + void InitFormRange(uint8_t *aStart, uint8_t *aEnd) { Base::InitFormRange(aStart, aEnd); } + + /** + * This template method initializes the `MutableData` to point to the content of an object. + * + * @tparm ObjectType The object type (MUST not be a pointer type). + * + * @param[in] aObject The object to initialize the `MutableData` with. + * + */ + template void InitFrom(ObjectType &aObject) + { + static_assert(!TypeTraits::IsPointer::kValue, "ObjectType MUST not be a pointer"); + Init(&aObject, sizeof(aObject)); + } + + /** + * This method returns a pointer to the data bytes buffer. + * + * @returns A pointer to the data bytes buffer (can be `nullptr` if `Data` is empty or uninitialized). + * + */ + uint8_t *GetBytes(void) { return AsNonConst(Base::GetBytes()); } + + /** + * This method returns a pointer to the data bytes buffer. + * + * @returns A pointer to the data bytes buffer (can be `nullptr` if `Data` is empty or uninitialized). + * + */ + const uint8_t *GetBytes(void) const { return Base::GetBytes(); } + + /** + * This method clears all the bytes (sets them to zero) in the buffer pointed by the `MutableData`. + * + */ + void ClearBytes(void) { memset(GetBytes(), 0, mLength); } + + /** + * This method copies the bytes from a given buffer into the `MutableData` buffer. + * + * If the current `MutableData` length is larger than or equal to @p aLength, then all the bytes are copied + * from @p aBuffer into the buffer of `MutableData` and the `MutableData`'s length is changed to @p aLength. + * + * If the current `MutableData` length is smaller than @p aLength, then the method returns `kErrorNoBufs` but still + * copies as many bytes as can fit. + * + * @param[in] aBuffer A pointer to a buffer to copy from. + * @param[in] aLength The length of @p aBuffer (number of bytes). + * + * @retval kErrorNone Successfully copied the bytes into `MutableData` buffer and adjusted its length. + * @retval kErrorNoBufs `MutableData` buffer cannot fit the given @p aLength bytes. + * + */ + Error CopyBytesFrom(const uint8_t *aBuffer, LengthType aLength) + { + Error error = (mLength >= aLength) ? kErrorNone : kErrorNoBufs; + + mLength = OT_MIN(mLength, aLength); + memcpy(AsNonConst(mBuffer), aBuffer, mLength); + + return error; + } + + /** + * This method copies the bytes from an given `Data` instance into the `MutableData` buffer. + * + * If the current `MutableData` length is larger than or equal to the @p aData length, then all the bytes are copied + * from @p aData into the buffer of `MutableData` and the `MutableData`'s length is adjusted accordingly. + * + * If the current `MutableData` length is smaller than @p aData length, then as many bytes as can fit are copied + * and the method returns `kErrorNoBufs`. + * + * @param[in] aData A `Data` instance to copy the content from. + * + * @retval kErrorNone Successfully copied the bytes into `MutableData` buffer and adjusted its length. + * @retval kErrorNoBufs `MutableData` buffer cannot fit the given @p aData bytes. + * + */ + Error CopyBytesFrom(const Data &aData) + { + return CopyBytesFrom(aData.GetBytes(), aData.GetLength()); + } +}; + +} // namespace ot + +#endif // DATA_HPP_ diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 6c41818c4..b733fdb2c 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -192,6 +192,27 @@ target_link_libraries(ot-test-cmd-line-parser add_test(NAME ot-test-cmd-line-parser COMMAND ot-test-cmd-line-parser) +add_executable(ot-test-data + test_data.cpp +) + +target_include_directories(ot-test-data + PRIVATE + ${COMMON_INCLUDES} +) + +target_compile_options(ot-test-data + PRIVATE + ${COMMON_COMPILE_OPTIONS} +) + +target_link_libraries(ot-test-data + PRIVATE + ${COMMON_LIBS} +) + +add_test(NAME ot-test-data COMMAND ot-test-data) + add_executable(ot-test-dns test_dns.cpp ) diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am index 277343f0e..140cbe2bb 100644 --- a/tests/unit/Makefile.am +++ b/tests/unit/Makefile.am @@ -113,6 +113,7 @@ check_PROGRAMS += \ ot-test-child \ ot-test-child-table \ ot-test-cmd-line-parser \ + ot-test-data \ ot-test-dns \ ot-test-ecdsa \ ot-test-flash \ @@ -197,6 +198,9 @@ ot_test_child_table_SOURCES = $(COMMON_SOURCES) test_child_table.cpp ot_test_cmd_line_parser_LDADD = $(COMMON_LDADD) ot_test_cmd_line_parser_SOURCES = $(COMMON_SOURCES) test_cmd_line_parser.cpp +ot_test_data_LDADD = $(COMMON_LDADD) +ot_test_data_SOURCES = $(COMMON_SOURCES) test_data.cpp + ot_test_dns_LDADD = $(COMMON_LDADD) ot_test_dns_SOURCES = $(COMMON_SOURCES) test_dns.cpp diff --git a/tests/unit/test_data.cpp b/tests/unit/test_data.cpp new file mode 100644 index 000000000..6c896976a --- /dev/null +++ b/tests/unit/test_data.cpp @@ -0,0 +1,208 @@ +/* + * 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 "test_platform.h" +#include "test_util.hpp" + +#include "common/data.hpp" + +namespace ot { + +template void TestData(void) +{ + typedef Data Data; + + const uint8_t kData[] = {0x12, 0x03, 0x19, 0x77}; + const uint8_t kDataCopy[] = {0x12, 0x03, 0x19, 0x77}; + + Data data; + Data data2; + uint8_t buffer[sizeof(kData) + 1]; + uint8_t u8; + uint16_t u16; + + data.Clear(); + data2.Clear(); + VerifyOrQuit(data.GetLength() == 0); + VerifyOrQuit(data.GetBytes() == nullptr); + VerifyOrQuit(data == data2); + VerifyOrQuit(data.StartsWith(data)); + VerifyOrQuit(data2.StartsWith(data)); + + data.Init(kData, sizeof(kData)); + VerifyOrQuit(data.GetLength() == sizeof(kData)); + VerifyOrQuit(data.GetBytes() == &kData[0]); + VerifyOrQuit(data == data); + VerifyOrQuit(data.StartsWith(data)); + + memset(buffer, 0, sizeof(buffer)); + data.CopyBytesTo(buffer); + VerifyOrQuit(memcmp(buffer, kData, sizeof(kData)) == 0); + VerifyOrQuit(buffer[sizeof(kData)] == 0); + + data2.InitFrom(kDataCopy); + VerifyOrQuit(data2.GetLength() == sizeof(kDataCopy)); + VerifyOrQuit(data2.GetBytes() == &kDataCopy[0]); + VerifyOrQuit(data == data2); + VerifyOrQuit(data.StartsWith(data2)); + VerifyOrQuit(data2.StartsWith(data)); + + data2.Init(kDataCopy, sizeof(kDataCopy) - 1); + VerifyOrQuit(data != data2); + VerifyOrQuit(data.StartsWith(data2)); + VerifyOrQuit(!data2.StartsWith(data)); + + data2.InitFromRange(&kDataCopy[0], &kDataCopy[2]); + VerifyOrQuit(data2.GetLength() == 2); + VerifyOrQuit(data2.GetBytes() == &kDataCopy[0]); + VerifyOrQuit(data != data2); + VerifyOrQuit(data.StartsWith(data2)); + VerifyOrQuit(!data2.StartsWith(data)); + + data2 = data; + VerifyOrQuit(data2 == data); + + data.Clear(); + VerifyOrQuit(data.GetLength() == 0); + VerifyOrQuit(data.GetBytes() == nullptr); + VerifyOrQuit(data != data2); + VerifyOrQuit(!data.StartsWith(data2)); + VerifyOrQuit(data2.StartsWith(data)); + + memset(buffer, 0xaa, sizeof(buffer)); + data.CopyBytesTo(buffer); + VerifyOrQuit(buffer[0] == 0xaa); + + data.InitFrom(u8); + VerifyOrQuit(data.GetLength() == sizeof(u8)); + VerifyOrQuit(data.GetBytes() == &u8); + + data.InitFrom(u16); + VerifyOrQuit(data.GetLength() == sizeof(u16)); + VerifyOrQuit(data.GetBytes() == reinterpret_cast(&u16)); + + printf("- TestData<%s> passed\n", kDataLengthType == kWithUint8Length ? "kWithUint8Length" : "kWithUint16Length"); +} + +template void TestMutableData(void) +{ + typedef Data Data; + typedef MutableData MutableData; + + constexpr uint8_t kMaxSize = 20; + + const uint8_t kData[] = {10, 20, 3, 15, 10, 00, 60, 16}; + const uint8_t kData2[] = {0xab, 0xbc, 0xcd, 0xde, 0xef}; + + MutableData mutableData; + Data data; + uint8_t buffer[kMaxSize]; + uint8_t u8; + uint16_t u16; + + data.Init(kData, sizeof(kData)); + + mutableData.Clear(); + VerifyOrQuit(mutableData.GetLength() == 0); + VerifyOrQuit(mutableData.GetBytes() == nullptr); + + mutableData.Init(buffer, sizeof(buffer)); + VerifyOrQuit(mutableData.GetLength() == sizeof(buffer)); + VerifyOrQuit(mutableData.GetBytes() == &buffer[0]); + + SuccessOrQuit(mutableData.CopyBytesFrom(kData, sizeof(kData))); + VerifyOrQuit(mutableData.GetLength() == sizeof(kData)); + VerifyOrQuit(mutableData.GetBytes() == &buffer[0]); + VerifyOrQuit(mutableData == data); + + SuccessOrQuit(mutableData.CopyBytesFrom(kData2, sizeof(kData2))); + VerifyOrQuit(mutableData.GetLength() == sizeof(kData2)); + VerifyOrQuit(mutableData.GetBytes() == &buffer[0]); + VerifyOrQuit(memcmp(mutableData.GetBytes(), kData2, sizeof(kData2)) == 0); + + memset(buffer, 0, sizeof(buffer)); + mutableData.InitFrom(buffer); + SuccessOrQuit(mutableData.CopyBytesFrom(kData, sizeof(kData))); + VerifyOrQuit(mutableData == data); + + memset(buffer, 0, sizeof(buffer)); + SuccessOrQuit(mutableData.CopyBytesFrom(data)); + VerifyOrQuit(mutableData == data); + + memset(buffer, 0, sizeof(buffer)); + mutableData.InitFromRange(&buffer[0], &buffer[2]); + VerifyOrQuit(mutableData.GetLength() == 2); + VerifyOrQuit(mutableData.GetBytes() == &buffer[0]); + + VerifyOrQuit(mutableData.CopyBytesFrom(kData, sizeof(kData)) == kErrorNoBufs); + VerifyOrQuit(mutableData.GetLength() == 2); + VerifyOrQuit(mutableData.GetBytes() == &buffer[0]); + VerifyOrQuit(memcmp(mutableData.GetBytes(), kData, 2) == 0); + + VerifyOrQuit(mutableData.CopyBytesFrom(data) == kErrorNoBufs); + VerifyOrQuit(mutableData.GetLength() == 2); + VerifyOrQuit(mutableData.GetBytes() == &buffer[0]); + VerifyOrQuit(memcmp(mutableData.GetBytes(), kData, 2) == 0); + + memset(buffer, 0xff, sizeof(buffer)); + mutableData.InitFrom(buffer); + VerifyOrQuit(mutableData.GetLength() == sizeof(buffer)); + VerifyOrQuit(mutableData.GetBytes() == &buffer[0]); + + u8 = 0xaa; + mutableData.InitFrom(u8); + VerifyOrQuit(mutableData.GetLength() == sizeof(u8)); + VerifyOrQuit(mutableData.GetBytes() == &u8); + mutableData.ClearBytes(); + VerifyOrQuit(u8 == 0); + + u16 = 0x1234; + mutableData.InitFrom(u16); + VerifyOrQuit(mutableData.GetLength() == sizeof(u16)); + VerifyOrQuit(mutableData.GetBytes() == reinterpret_cast(&u16)); + mutableData.ClearBytes(); + VerifyOrQuit(u16 == 0); + + printf("- TestMutableData<%s> passed\n", + kDataLengthType == kWithUint8Length ? "kWithUint8Length" : "kWithUint16Length"); +} + +} // namespace ot + +int main(void) +{ + ot::TestData(); + ot::TestData(); + ot::TestMutableData(); + ot::TestMutableData(); + + printf("All tests passed\n"); + return 0; +}