diff --git a/Android.mk b/Android.mk index 7da37328b..b51f020c9 100644 --- a/Android.mk +++ b/Android.mk @@ -219,6 +219,7 @@ LOCAL_SRC_FILES := \ src/core/coap/coap.cpp \ src/core/coap/coap_message.cpp \ src/core/coap/coap_secure.cpp \ + src/core/common/appender.cpp \ src/core/common/crc16.cpp \ src/core/common/error.cpp \ src/core/common/heap.cpp \ diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index 0be95fe41..3db50cae4 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -371,6 +371,8 @@ openthread_core_files = [ "coap/coap_message.hpp", "coap/coap_secure.cpp", "coap/coap_secure.hpp", + "common/appender.cpp", + "common/appender.hpp", "common/arg_macros.hpp", "common/array.hpp", "common/as_core_type.hpp", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index d2406a71d..3d1c4d791 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -92,6 +92,7 @@ set(COMMON_SOURCES coap/coap.cpp coap/coap_message.cpp coap/coap_secure.cpp + common/appender.cpp common/crc16.cpp common/error.cpp common/heap.cpp diff --git a/src/core/Makefile.am b/src/core/Makefile.am index d91114a5e..8cdd27465 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -182,6 +182,7 @@ SOURCES_COMMON = \ coap/coap.cpp \ coap/coap_message.cpp \ coap/coap_secure.cpp \ + common/appender.cpp \ common/crc16.cpp \ common/error.cpp \ common/heap.cpp \ @@ -414,6 +415,7 @@ HEADERS_COMMON = \ coap/coap.hpp \ coap/coap_message.hpp \ coap/coap_secure.hpp \ + common/appender.hpp \ common/arg_macros.hpp \ common/array.hpp \ common/as_core_type.hpp \ diff --git a/src/core/common/appender.cpp b/src/core/common/appender.cpp new file mode 100644 index 000000000..f312f4202 --- /dev/null +++ b/src/core/common/appender.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 `Appender` class. + */ + +#include "appender.hpp" + +namespace ot { + +Appender::Appender(Message &aMessage) + : mType(kMessage) +{ + mShared.mMessage.mMessage = &aMessage; + mShared.mMessage.mStartOffset = aMessage.GetLength(); +} + +Appender::Appender(uint8_t *aBuffer, uint16_t aSize) + : mType(kBuffer) +{ + mShared.mBuffer.mStart = aBuffer; + mShared.mBuffer.mCur = aBuffer; + mShared.mBuffer.mEnd = aBuffer + aSize; +} + +Error Appender::AppendBytes(const void *aBuffer, uint16_t aLength) +{ + Error error = kErrorNone; + + switch (mType) + { + case kMessage: + error = mShared.mMessage.mMessage->AppendBytes(aBuffer, aLength); + break; + + case kBuffer: + VerifyOrExit(aLength <= static_cast(mShared.mBuffer.mEnd - mShared.mBuffer.mCur), + error = kErrorNoBufs); + memcpy(mShared.mBuffer.mCur, aBuffer, aLength); + mShared.mBuffer.mCur += aLength; + break; + } + +exit: + return error; +} + +uint16_t Appender::GetAppendedLength(void) const +{ + uint16_t length = 0; + + switch (mType) + { + case kMessage: + length = mShared.mMessage.mMessage->GetLength() - mShared.mMessage.mStartOffset; + break; + + case kBuffer: + length = static_cast(mShared.mBuffer.mCur - mShared.mBuffer.mStart); + break; + } + + return length; +} + +void Appender::GetAsData(Data &aData) +{ + aData.Init(mShared.mBuffer.mStart, GetAppendedLength()); +} + +} // namespace ot diff --git a/src/core/common/appender.hpp b/src/core/common/appender.hpp new file mode 100644 index 000000000..efefb9416 --- /dev/null +++ b/src/core/common/appender.hpp @@ -0,0 +1,186 @@ +/* + * 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 defines OpenThread `Appender` class. + */ + +#ifndef APPENDER_HPP_ +#define APPENDER_HPP_ + +#include "openthread-core-config.h" + +#include "common/data.hpp" +#include "common/message.hpp" +#include "common/type_traits.hpp" + +namespace ot { + +/** + * The `Appender` class acts as a wrapper over either a `Message` or a data buffer and provides different flavors of + * `Append()` method. + * + * This class helps in construction of message content where the destination can be either a `Message` or a buffer. + * + */ +class Appender +{ +public: + /** + * This enumeration represent the `Appender` Type (whether appending to a `Message` or data buffer). + * + */ + enum Type : uint8_t + { + kMessage, ///< `Appender` appends to a `Message` + kBuffer, ///< `Appender` appends to a buffer. + }; + + /** + * This constructor initializes the `Appender` to append to a `Message`. + * + * New content is appended to the end of @p aMessage, growing its length. + * + * @param[in] aMessage The message to append to. + * + */ + explicit Appender(Message &aMessage); + + /** + * This constructor initializes the `Appender` to append in a given a buffer + * + * New content is append in the buffer starting from @p aBuffer up to is size @p aSize. `Appender` does not allow + * content to be appended beyond the size of the buffer. + * + * @param[in] aBuffer A pointer to start of buffer. + * @param[in] aSize The maximum size of @p aBuffer (number of available bytes in buffer). + * + */ + Appender(uint8_t *aBuffer, uint16_t aSize); + + /** + * This method indicates the `Appender` type (whether appending to a `Message` or data buffer). + * + * @returns The type of `Appender`. + * + */ + Type GetType(void) const { return mType; } + + /** + * This method appends bytes to the `Appender` object + * + * @param[in] aBuffer A pointer to a data buffer (MUST NOT be `nullptr`) to append. + * @param[in] aLength The number of bytes to append. + * + * @retval kErrorNone Successfully appended the bytes. + * @retval kErrorNoBufs Insufficient available buffers. + * + */ + Error AppendBytes(const void *aBuffer, uint16_t aLength); + + /** + * This method appends an object to the end of the `Appender` object. + * + * @tparam ObjectType The object type to append to the message. + * + * @param[in] aObject A reference to the object to append to the message. + * + * @retval kErrorNone Successfully appended the object. + * @retval kErrorNoBufs Insufficient available buffers to append @p aObject. + * + */ + template Error Append(const ObjectType &aObject) + { + static_assert(!TypeTraits::IsPointer::kValue, "ObjectType must not be a pointer"); + + return AppendBytes(&aObject, sizeof(ObjectType)); + } + + /** + * This method returns the number of bytes appended so far using `Appender` methods. + * + * This method can be used independent of the `Type` of `Appender`. + * + * @returns The number of byes appended so far. + * + */ + uint16_t GetAppendedLength(void) const; + + /** + * This method returns the `Message` associated with `Appender`. + * + * This method MUST be used when `GetType() == kMessage`. Otherwise its behavior is undefined. + * + * @returns The `Message` instance associated with `Appender`. + * + */ + Message &GetMessage(void) { return *mShared.mMessage.mMessage; } + + /** + * This method returns a pointer to the start of the data buffer associated with `Appender`. + * + * This method MUST be used when `GetType() == kBuffer`. Otherwise its behavior is undefined. + * + * @returns A pointer to the start of the data buffer associated with `Appender`. + * + */ + uint8_t *GetBufferStart(void) { return mShared.mBuffer.mStart; } + + /** + * This method gets the data buffer associated with `Appender` as a `Data`. + * + * This method MUST be used when `GetType() == kBuffer`. Otherwise its behavior is undefined. + * + * @pram[out] aData A reference to a `Data` to output the data buffer. + * + */ + void GetAsData(Data &aData); + +private: + Type mType; + union + { + struct + { + Message *mMessage; + uint16_t mStartOffset; + } mMessage; + + struct + { + uint8_t *mStart; + uint8_t *mCur; + uint8_t *mEnd; + } mBuffer; + } mShared; +}; + +} // namespace ot + +#endif // APPENDER_HPP_ diff --git a/tests/unit/test_message.cpp b/tests/unit/test_message.cpp index 268620f81..5c8172044 100644 --- a/tests/unit/test_message.cpp +++ b/tests/unit/test_message.cpp @@ -26,6 +26,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#include "common/appender.hpp" #include "common/debug.hpp" #include "common/instance.hpp" #include "common/message.hpp" @@ -53,6 +54,8 @@ void TestMessage(void) uint8_t readBuffer[kMaxSize]; uint8_t zeroBuffer[kMaxSize]; + printf("TestMessage\n"); + memset(zeroBuffer, 0, sizeof(zeroBuffer)); instance = static_cast(testInitInstance()); @@ -236,11 +239,101 @@ void TestMessage(void) testFreeInstance(instance); } +void TestAppender(void) +{ + const uint8_t kData1[] = {0x01, 0x02, 0x03, 0x04}; + const uint8_t kData2[] = {0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa}; + + static constexpr uint16_t kMaxBufferSize = sizeof(kData1) * 2 + sizeof(kData2); + + Instance * instance; + Message * message; + uint8_t buffer[kMaxBufferSize]; + uint8_t zeroBuffer[kMaxBufferSize]; + Appender bufAppender(buffer, sizeof(buffer)); + Data data; + + printf("TestAppender\n"); + + instance = static_cast(testInitInstance()); + VerifyOrQuit(instance != nullptr); + + message = instance->Get().New(Message::kTypeIp6, 0); + VerifyOrQuit(message != nullptr); + + memset(buffer, 0, sizeof(buffer)); + memset(zeroBuffer, 0, sizeof(zeroBuffer)); + + // Test Buffer Appender + VerifyOrQuit(bufAppender.GetType() == Appender::kBuffer); + VerifyOrQuit(bufAppender.GetBufferStart() == buffer); + VerifyOrQuit(bufAppender.GetAppendedLength() == 0); + + SuccessOrQuit(bufAppender.AppendBytes(kData1, sizeof(kData1))); + DumpBuffer("Data1", buffer, sizeof(buffer)); + VerifyOrQuit(bufAppender.GetAppendedLength() == sizeof(kData1)); + VerifyOrQuit(bufAppender.GetBufferStart() == buffer); + VerifyOrQuit(memcmp(buffer, kData1, sizeof(kData1)) == 0); + VerifyOrQuit(memcmp(buffer + sizeof(kData1), zeroBuffer, sizeof(buffer) - sizeof(kData1)) == 0); + + SuccessOrQuit(bufAppender.AppendBytes(kData2, sizeof(kData2))); + DumpBuffer("Data1+Data2", buffer, sizeof(buffer)); + VerifyOrQuit(bufAppender.GetAppendedLength() == sizeof(kData1) + sizeof(kData2)); + VerifyOrQuit(bufAppender.GetBufferStart() == buffer); + VerifyOrQuit(memcmp(buffer, kData1, sizeof(kData1)) == 0); + VerifyOrQuit(memcmp(buffer + sizeof(kData1), kData2, sizeof(kData2)) == 0); + VerifyOrQuit(memcmp(buffer + sizeof(kData1) + sizeof(kData2), zeroBuffer, + sizeof(buffer) - sizeof(kData1) - sizeof(kData2)) == 0); + + VerifyOrQuit(bufAppender.Append(kData2) == kErrorNoBufs); + + SuccessOrQuit(bufAppender.AppendBytes(kData1, sizeof(kData1))); + DumpBuffer("Data1+Data2+Data1", buffer, sizeof(buffer)); + VerifyOrQuit(bufAppender.GetAppendedLength() == sizeof(kData1) + sizeof(kData2) + sizeof(kData1)); + VerifyOrQuit(bufAppender.GetBufferStart() == buffer); + VerifyOrQuit(memcmp(buffer, kData1, sizeof(kData1)) == 0); + VerifyOrQuit(memcmp(buffer + sizeof(kData1), kData2, sizeof(kData2)) == 0); + VerifyOrQuit(memcmp(buffer + sizeof(kData1) + sizeof(kData2), kData1, sizeof(kData1)) == 0); + + VerifyOrQuit(bufAppender.Append(0) == kErrorNoBufs); + + bufAppender.GetAsData(data); + VerifyOrQuit(data.GetBytes() == buffer); + VerifyOrQuit(data.GetLength() == sizeof(buffer)); + + // Test Message Appender + + SuccessOrQuit(message->Append(kData2)); + VerifyOrQuit(message->Compare(0, kData2)); + + { + Appender msgAppender(*message); + uint16_t offset = message->GetLength(); + + VerifyOrQuit(msgAppender.GetType() == Appender::kMessage); + + SuccessOrQuit(msgAppender.AppendBytes(kData1, sizeof(kData1))); + VerifyOrQuit(msgAppender.GetAppendedLength() == sizeof(kData1)); + + VerifyOrQuit(message->GetLength() == sizeof(kData2) + sizeof(kData1)); + VerifyOrQuit(message->Compare(offset, kData1)); + + SuccessOrQuit(msgAppender.AppendBytes(kData2, sizeof(kData2))); + VerifyOrQuit(msgAppender.GetAppendedLength() == sizeof(kData1) + sizeof(kData2)); + VerifyOrQuit(message->Compare(offset, kData1)); + VerifyOrQuit(message->Compare(offset + sizeof(kData1), kData2)); + } + + message->Free(); + testFreeInstance(instance); +} + } // namespace ot int main(void) { ot::TestMessage(); + ot::TestAppender(); printf("All tests passed\n"); return 0; }