From 7eb59f71da4be41ea490b21397bf4569ef28fa6b Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Fri, 17 Apr 2026 11:21:59 -0700 Subject: [PATCH] [network-diag] introduce `AnswerBuilder` to manage answer messages (#12887) This commit introduces `AnswerBuilder` class to track and manage Network Diagnostic answer messages. This class is used when the response to a query requires multiple CoAP answer messages. It automatically manages the inclusion of the Query ID and the Answer TLVs(providing message indexing and "more-to-follow" flags) in each allocated answer message, while maintaining all answer messages in a queue. The `NetworkDiagnostic::Server` is updated to use the `AnswerBuilder`, simplifying the logic for preparing and sending answers. The `AnswerBuilder` class is added in a new header file `network_diagnostic_types.hpp` to allow for its reuse by other modules in the future. --- src/core/BUILD.gn | 2 + src/core/CMakeLists.txt | 1 + src/core/common/message.cpp | 24 ++++ src/core/common/message.hpp | 9 ++ src/core/thread/network_diagnostic.cpp | 122 +++++----------- src/core/thread/network_diagnostic.hpp | 34 +---- src/core/thread/network_diagnostic_types.cpp | 108 ++++++++++++++ src/core/thread/network_diagnostic_types.hpp | 139 +++++++++++++++++++ tests/unit/test_message_queue.cpp | 54 +++++++ 9 files changed, 380 insertions(+), 113 deletions(-) create mode 100644 src/core/thread/network_diagnostic_types.cpp create mode 100644 src/core/thread/network_diagnostic_types.hpp diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index ff3091be0..1bc0a857a 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -755,6 +755,8 @@ openthread_core_files = [ "thread/network_diagnostic.hpp", "thread/network_diagnostic_tlvs.cpp", "thread/network_diagnostic_tlvs.hpp", + "thread/network_diagnostic_types.cpp", + "thread/network_diagnostic_types.hpp", "thread/panid_query_server.cpp", "thread/panid_query_server.hpp", "thread/peer.cpp", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 60503c96f..ef1a4115e 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -268,6 +268,7 @@ set(COMMON_SOURCES thread/network_data_types.cpp thread/network_diagnostic.cpp thread/network_diagnostic_tlvs.cpp + thread/network_diagnostic_types.cpp thread/panid_query_server.cpp thread/peer.cpp thread/peer_table.cpp diff --git a/src/core/common/message.cpp b/src/core/common/message.cpp index 03deb47d5..29e917efa 100644 --- a/src/core/common/message.cpp +++ b/src/core/common/message.cpp @@ -980,6 +980,30 @@ void MessageQueue::DequeueAndFreeAll(void) } } +void MessageQueue::EnqueueAllFrom(MessageQueue &aOtherQueue) +{ + VerifyOrExit(&aOtherQueue != this); + + VerifyOrExit(aOtherQueue.GetHead() != nullptr); + + if (GetHead() == nullptr) + { + SetHead(aOtherQueue.GetHead()); + } + else + { + GetTail()->Next() = aOtherQueue.GetHead(); + aOtherQueue.GetHead()->Prev() = GetTail(); + } + + SetTail(aOtherQueue.GetTail()); + + aOtherQueue.Clear(); + +exit: + return; +} + Message::Iterator MessageQueue::begin(void) { return Message::Iterator(GetHead()); } Message::ConstIterator MessageQueue::begin(void) const { return Message::ConstIterator(GetHead()); } diff --git a/src/core/common/message.hpp b/src/core/common/message.hpp index 06ef455b2..2e0c3b3ed 100644 --- a/src/core/common/message.hpp +++ b/src/core/common/message.hpp @@ -1731,6 +1731,15 @@ public: */ void DequeueAndFreeAll(void); + /** + * Enqueues all messages from another message queue at the end of this queue. + * + * Upon return, @p aOtherQueue will be empty. + * + * @param[in,out] aOtherQueue The other message queue to enqueue from. + */ + void EnqueueAllFrom(MessageQueue &aOtherQueue); + /** * Gets the information about number of messages and buffers in the queue. * diff --git a/src/core/thread/network_diagnostic.cpp b/src/core/thread/network_diagnostic.cpp index 6e0e1e550..50d3b1c11 100644 --- a/src/core/thread/network_diagnostic.cpp +++ b/src/core/thread/network_diagnostic.cpp @@ -539,34 +539,6 @@ exit: #if OPENTHREAD_FTD -Error Server::AllocateAnswer(Coap::Message *&aAnswer, AnswerInfo &aInfo) -{ - // Allocate an `Answer` message, adds it in `mAnswerQueue`, - // update the `aInfo.mFirstAnswer` if it is the first allocated - // messages, and appends `QueryIdTlv` to the message (if needed). - - Error error = kErrorNone; - - aAnswer = Get().AllocateAndInitConfirmablePostMessage(kUriDiagnosticGetAnswer); - VerifyOrExit(aAnswer != nullptr, error = kErrorNoBufs); - IgnoreError(aAnswer->SetPriority(aInfo.mPriority)); - - mAnswerQueue.Enqueue(*aAnswer); - - if (aInfo.mFirstAnswer == nullptr) - { - aInfo.mFirstAnswer = aAnswer; - } - - if (aInfo.mHasQueryId) - { - SuccessOrExit(error = Tlv::Append(*aAnswer, aInfo.mQueryId)); - } - -exit: - return error; -} - bool Server::IsLastAnswer(const Coap::Message &aAnswer) const { // Indicates whether `aAnswer` is the last one associated with @@ -601,23 +573,29 @@ void Server::FreeAllRelatedAnswers(Coap::Message &aFirstAnswer) } } -void Server::PrepareAndSendAnswers(const Ip6::Address &aDestination, const Message &aRequest) +void Server::PrepareAndSendAnswers(const Ip6::Address &aDestination, const Coap::Message &aRequest) +{ + AnswerBuilder answerBuilder(GetInstance()); + Coap::Message *firstAnswer; + + SuccessOrExit(PrepareAnswers(aRequest, answerBuilder)); + + firstAnswer = answerBuilder.GetAnswers().GetHead(); + mAnswerQueue.EnqueueAllFrom(answerBuilder.GetAnswers()); + + SendNextAnswer(*firstAnswer, aDestination); + +exit: + return; +} + +Error Server::PrepareAnswers(const Coap::Message &aRequest, AnswerBuilder &aAnswerBuilder) { - Coap::Message *answer; Error error; - AnswerInfo info; uint8_t tlvType; TlvTypeListIterator iterator; - AnswerTlvValue answerTlvValue; - if (Tlv::Find(aRequest, info.mQueryId) == kErrorNone) - { - info.mHasQueryId = true; - } - - info.mPriority = aRequest.GetPriority(); - - SuccessOrExit(error = AllocateAnswer(answer, info)); + SuccessOrExit(error = aAnswerBuilder.Start(aRequest)); SuccessOrExit(error = iterator.InitForTypeListTlv(aRequest)); @@ -626,51 +604,23 @@ void Server::PrepareAndSendAnswers(const Ip6::Address &aDestination, const Messa switch (tlvType) { case ChildTlv::kType: - SuccessOrExit(error = AppendChildTableAsChildTlvs(answer, info)); + SuccessOrExit(error = AppendChildTableAsChildTlvs(aAnswerBuilder)); break; case ChildIp6AddressListTlv::kType: - SuccessOrExit(error = AppendChildTableIp6AddressList(answer, info)); + SuccessOrExit(error = AppendChildTableIp6AddressList(aAnswerBuilder)); break; case RouterNeighborTlv::kType: - SuccessOrExit(error = AppendRouterNeighborTlvs(answer, info)); + SuccessOrExit(error = AppendRouterNeighborTlvs(aAnswerBuilder)); break; default: - SuccessOrExit(error = AppendDiagTlv(tlvType, *answer)); + SuccessOrExit(error = AppendDiagTlv(tlvType, aAnswerBuilder.GetAnswer())); break; } - SuccessOrExit(error = CheckAnswerLength(answer, info)); + SuccessOrExit(error = aAnswerBuilder.CheckAnswerLength()); } - answerTlvValue.Init(info.mAnswerIndex, AnswerTlvValue::kIsLast); - SuccessOrExit(error = Tlv::Append(*answer, answerTlvValue)); - - SendNextAnswer(*info.mFirstAnswer, aDestination); - -exit: - if ((error != kErrorNone) && (info.mFirstAnswer != nullptr)) - { - FreeAllRelatedAnswers(*info.mFirstAnswer); - } -} - -Error Server::CheckAnswerLength(Coap::Message *&aAnswer, AnswerInfo &aInfo) -{ - // This method checks the length of the `aAnswer` message and if it - // is above the threshold, it enqueues the message for transmission - // after appending an Answer TLV with the current index to the - // message. In this case, it will also allocate a new answer - // message. - - Error error = kErrorNone; - AnswerTlvValue answerTlvValue; - - VerifyOrExit(aAnswer->GetLength() >= kAnswerMessageLengthThreshold); - - answerTlvValue.Init(aInfo.mAnswerIndex++, AnswerTlvValue::kMoreToFollow); - SuccessOrExit(error = Tlv::Append(*aAnswer, answerTlvValue)); - - error = AllocateAnswer(aAnswer, aInfo); + SuccessOrExit(error = aAnswerBuilder.Finish()); exit: return error; @@ -734,7 +684,7 @@ exit: } } -Error Server::AppendChildTableAsChildTlvs(Coap::Message *&aAnswer, AnswerInfo &aInfo) +Error Server::AppendChildTableAsChildTlvs(AnswerBuilder &aAnswerBuilder) { Error error = kErrorNone; ChildTlvValue childTlvValue; @@ -743,17 +693,17 @@ Error Server::AppendChildTableAsChildTlvs(Coap::Message *&aAnswer, AnswerInfo &a { childTlvValue.InitFrom(child); - SuccessOrExit(error = Tlv::Append(*aAnswer, childTlvValue)); - SuccessOrExit(error = CheckAnswerLength(aAnswer, aInfo)); + SuccessOrExit(error = Tlv::Append(aAnswerBuilder.GetAnswer(), childTlvValue)); + SuccessOrExit(error = aAnswerBuilder.CheckAnswerLength()); } - error = Tlv::AppendEmpty(*aAnswer); + error = Tlv::AppendEmpty(aAnswerBuilder.GetAnswer()); exit: return error; } -Error Server::AppendRouterNeighborTlvs(Coap::Message *&aAnswer, AnswerInfo &aInfo) +Error Server::AppendRouterNeighborTlvs(AnswerBuilder &aAnswerBuilder) { Error error = kErrorNone; RouterNeighborTlvValue neighborTlvValue; @@ -767,27 +717,27 @@ Error Server::AppendRouterNeighborTlvs(Coap::Message *&aAnswer, AnswerInfo &aInf neighborTlvValue.InitFrom(router); - SuccessOrExit(error = Tlv::Append(*aAnswer, neighborTlvValue)); - SuccessOrExit(error = CheckAnswerLength(aAnswer, aInfo)); + SuccessOrExit(error = Tlv::Append(aAnswerBuilder.GetAnswer(), neighborTlvValue)); + SuccessOrExit(error = aAnswerBuilder.CheckAnswerLength()); } - error = Tlv::AppendEmpty(*aAnswer); + error = Tlv::AppendEmpty(aAnswerBuilder.GetAnswer()); exit: return error; } -Error Server::AppendChildTableIp6AddressList(Coap::Message *&aAnswer, AnswerInfo &aInfo) +Error Server::AppendChildTableIp6AddressList(AnswerBuilder &aAnswerBuilder) { Error error = kErrorNone; for (const Child &child : Get().Iterate(Child::kInStateValid)) { - SuccessOrExit(error = AppendChildIp6AddressListTlv(*aAnswer, child)); - SuccessOrExit(error = CheckAnswerLength(aAnswer, aInfo)); + SuccessOrExit(error = AppendChildIp6AddressListTlv(aAnswerBuilder.GetAnswer(), child)); + SuccessOrExit(error = aAnswerBuilder.CheckAnswerLength()); } - error = Tlv::AppendEmpty(*aAnswer); + error = Tlv::AppendEmpty(aAnswerBuilder.GetAnswer()); exit: return error; diff --git a/src/core/thread/network_diagnostic.hpp b/src/core/thread/network_diagnostic.hpp index d73bc91af..6545ff8cc 100644 --- a/src/core/thread/network_diagnostic.hpp +++ b/src/core/thread/network_diagnostic.hpp @@ -45,6 +45,7 @@ #include "common/numeric_limits.hpp" #include "net/udp6.hpp" #include "thread/network_diagnostic_tlvs.hpp" +#include "thread/network_diagnostic_types.hpp" #include "thread/tmf.hpp" #include "thread/uri_paths.hpp" @@ -119,8 +120,7 @@ public: } private: - static constexpr uint16_t kMaxChildEntries = 398; - static constexpr uint16_t kAnswerMessageLengthThreshold = 800; + static constexpr uint16_t kMaxChildEntries = 398; class TlvTypeListIterator { @@ -139,25 +139,6 @@ private: BitSet::kMax + 1> mProcessedTlvs; }; -#if OPENTHREAD_FTD - struct AnswerInfo - { - AnswerInfo(void) - : mAnswerIndex(0) - , mQueryId(0) - , mHasQueryId(false) - , mFirstAnswer(nullptr) - { - } - - uint16_t mAnswerIndex; - uint16_t mQueryId; - bool mHasQueryId; - Message::Priority mPriority; - Coap::Message *mFirstAnswer; - }; -#endif - Error AppendDiagTlv(uint8_t aTlvType, Message &aMessage); Error AppendIp6AddressList(Message &aMessage); Error AppendRequestedTlvs(const Message &aRequest, Message &aResponse); @@ -169,16 +150,15 @@ private: #if OPENTHREAD_MTD void SendAnswer(const Ip6::Address &aDestination, const Message &aRequest); #elif OPENTHREAD_FTD - Error AllocateAnswer(Coap::Message *&aAnswer, AnswerInfo &aInfo); - Error CheckAnswerLength(Coap::Message *&aAnswer, AnswerInfo &aInfo); bool IsLastAnswer(const Coap::Message &aAnswer) const; void FreeAllRelatedAnswers(Coap::Message &aFirstAnswer); - void PrepareAndSendAnswers(const Ip6::Address &aDestination, const Message &aRequest); + void PrepareAndSendAnswers(const Ip6::Address &aDestination, const Coap::Message &aRequest); + Error PrepareAnswers(const Coap::Message &aRequest, AnswerBuilder &aAnswerBuilder); void SendNextAnswer(Coap::Message &aAnswer, const Ip6::Address &aDestination); Error AppendChildTable(Message &aMessage); - Error AppendChildTableAsChildTlvs(Coap::Message *&aAnswer, AnswerInfo &aInfo); - Error AppendRouterNeighborTlvs(Coap::Message *&aAnswer, AnswerInfo &aInfo); - Error AppendChildTableIp6AddressList(Coap::Message *&aAnswer, AnswerInfo &aInfo); + Error AppendChildTableAsChildTlvs(AnswerBuilder &aAnswerBuilder); + Error AppendRouterNeighborTlvs(AnswerBuilder &aAnswerBuilder); + Error AppendChildTableIp6AddressList(AnswerBuilder &aAnswerBuilder); Error AppendChildIp6AddressListTlv(Message &aAnswer, const Child &aChild); Error AppendEnhancedRoute(Message &aMessage); diff --git a/src/core/thread/network_diagnostic_types.cpp b/src/core/thread/network_diagnostic_types.cpp new file mode 100644 index 000000000..56267730f --- /dev/null +++ b/src/core/thread/network_diagnostic_types.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2026, 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 methods for Network Diagnostics helper types and classes. + */ + +#include "network_diagnostic_types.hpp" + +#include "instance/instance.hpp" + +namespace ot { +namespace NetworkDiagnostic { + +AnswerBuilder::AnswerBuilder(Instance &aInstance) + : InstanceLocator(aInstance) + , mAnswer(nullptr) + , mAnswerIndex(0) + , mQueryId(0) + , mHasQueryId(false) + , mPriority(Message::kPriorityNormal) +{ +} + +AnswerBuilder::~AnswerBuilder(void) { mAnswers.DequeueAndFreeAll(); } + +Error AnswerBuilder::Start(const Coap::Message &aRequest) +{ + mAnswerIndex = 0; + mHasQueryId = (Tlv::Find(aRequest, mQueryId) == kErrorNone); + mPriority = aRequest.GetPriority(); + + return Allocate(); +} + +Error AnswerBuilder::Finish(void) { return AppendAnswerTlv(AnswerTlvValue::kIsLast); } + +Error AnswerBuilder::Allocate(void) +{ + Error error = kErrorNone; + + mAnswer = Get().AllocateAndInitConfirmablePostMessage(kUriDiagnosticGetAnswer); + VerifyOrExit(mAnswer != nullptr, error = kErrorNoBufs); + + IgnoreError(mAnswer->SetPriority(mPriority)); + + mAnswers.Enqueue(*mAnswer); + + if (mHasQueryId) + { + SuccessOrExit(error = Tlv::Append(*mAnswer, mQueryId)); + } + +exit: + return error; +} + +Error AnswerBuilder::AppendAnswerTlv(AnswerTlvValue::IsLastFlag aFlag) +{ + AnswerTlvValue value; + + value.Init(mAnswerIndex++, aFlag); + + return Tlv::Append(*mAnswer, value); +} + +Error AnswerBuilder::CheckAnswerLength(void) +{ + Error error = kErrorNone; + + VerifyOrExit(mAnswer->GetLength() >= kAnswerMessageLengthThreshold); + + SuccessOrExit(error = AppendAnswerTlv(AnswerTlvValue::kMoreToFollow)); + + error = Allocate(); + +exit: + return error; +} + +} // namespace NetworkDiagnostic +} // namespace ot diff --git a/src/core/thread/network_diagnostic_types.hpp b/src/core/thread/network_diagnostic_types.hpp new file mode 100644 index 000000000..2038b9e01 --- /dev/null +++ b/src/core/thread/network_diagnostic_types.hpp @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2026, 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 Network Diagnostics helper types and classes. + */ + +#ifndef OT_CORE_THREAD_NETWORK_DIAGNOSTIC_TYPES_HPP_ +#define OT_CORE_THREAD_NETWORK_DIAGNOSTIC_TYPES_HPP_ + +#include "openthread-core-config.h" + +#include "network_diagnostic_tlvs.hpp" +#include "coap/coap_message.hpp" +#include "common/error.hpp" +#include "common/locator.hpp" +#include "common/time.hpp" +#include "thread/uri_paths.hpp" + +namespace ot { +namespace NetworkDiagnostic { + +/** + * Represents an object for tracking and managing Network Diagnostic answer messages. + * + * This class is used when the response to a Network Diagnostic query requires multiple CoAP answer messages . It + * manages the inclusion of the Query ID and the Answer TLV (providing message indexing and "more-to-follow" flags) + * in each allocated answer message, while maintaining all answer messages in a queue. + */ +class AnswerBuilder : public InstanceLocator +{ +public: + /** + * Initializes the `AnswerBuilder`. + * + * @param[in] aInstance The OpenThread instance. + */ + explicit AnswerBuilder(Instance &aInstance); + + /** + * Destructor for `AnswerBuilder`. + * + * Any remaining messages in the answers queue are freed. + */ + ~AnswerBuilder(void); + + /** + * Starts the building of answer messages based on a given Network Diagnostic request. + * + * This method searches for a Query ID TLV within @p aRequest. If present, the Query ID is captured and + * automatically included in all allocated answer messages. It also captures the priority from @p aRequest and + * uses it for all answer messages. + * + * @param[in] aRequest The Network Diagnostic request message. + * + * @retval kErrorNone Successfully started. + * @retval kErrorNoBufs Insufficient message buffers to allocate the first answer. + */ + Error Start(const Coap::Message &aRequest); + + /** + * Gets the current answer message. + * + * @returns The current answer message. + */ + Coap::Message &GetAnswer(void) { return *mAnswer; } + + /** + * Checks the current answer message length and allocates a new one if it exceeds the threshold. + * + * If the current answer message length is above the threshold, this method appends an Answer TLV with the + * "more to follow" flag set, and then allocates a new answer message, adding it to the answer queue. + * + * @retval kErrorNone Successfully checked and handled (possibly allocating a new message). + * @retval kErrorNoBufs Insufficient message buffers to append Answer TLV or allocate a new message. + */ + Error CheckAnswerLength(void); + + /** + * Finishes the answer generation. + * + * This method appends an Answer TLV to the current answer message with the "is last" flag set. + * + * @retval kErrorNone Successfully finished. + * @retval kErrorNoBufs Insufficient message buffers to append the Answer TLV. + */ + Error Finish(void); + + /** + * Gets the queue containing all answer messages. + * + * @returns The message queue containing all answer messages. + */ + Coap::MessageQueue &GetAnswers(void) { return mAnswers; } + +private: + static constexpr uint16_t kAnswerMessageLengthThreshold = 800; + + Error Allocate(void); + Error AppendAnswerTlv(AnswerTlvValue::IsLastFlag aFlag); + + Coap::Message *mAnswer; + Coap::MessageQueue mAnswers; + uint16_t mAnswerIndex; + uint16_t mQueryId; + bool mHasQueryId; + Message::Priority mPriority; +}; + +} // namespace NetworkDiagnostic +} // namespace ot + +#endif // OT_CORE_THREAD_NETWORK_DIAGNOSTIC_TYPES_HPP_ diff --git a/tests/unit/test_message_queue.cpp b/tests/unit/test_message_queue.cpp index 1612f13d3..54eb49e9c 100644 --- a/tests/unit/test_message_queue.cpp +++ b/tests/unit/test_message_queue.cpp @@ -286,6 +286,59 @@ void TestMessageQueue(void) testFreeInstance(sInstance); } +void TestMessageQueueEnqueueAllFrom(void) +{ + // Validates the behavior of `MessageQueue::EnqueueAllFrom()` method. + + MessageQueue messageQueue1; + MessageQueue messageQueue2; + Message *messages[kNumTestMessages]; + + sInstance = testInitInstance(); + VerifyOrQuit(sInstance != nullptr); + + sMessagePool = &sInstance->Get(); + + for (Message *&msg : messages) + { + msg = sMessagePool->Allocate(Message::kTypeIp6); + VerifyOrQuit(msg != nullptr, "Message::Allocate() failed"); + } + + // Case 1: Both queues are empty + messageQueue1.EnqueueAllFrom(messageQueue2); + VerifyMessageQueueContent(messageQueue1, 0); + VerifyMessageQueueContent(messageQueue2, 0); + + // Case 2: Target queue (Q1) is empty, input queue (Q2) is non-empty + messageQueue2.Enqueue(*messages[0]); + messageQueue2.Enqueue(*messages[1]); + VerifyMessageQueueContent(messageQueue2, 2, messages[0], messages[1]); + messageQueue1.EnqueueAllFrom(messageQueue2); + VerifyMessageQueueContent(messageQueue1, 2, messages[0], messages[1]); + VerifyMessageQueueContent(messageQueue2, 0); + + // Case 3: Target queue (Q1) is non-empty, input queue (Q2) is empty + messageQueue1.EnqueueAllFrom(messageQueue2); + VerifyMessageQueueContent(messageQueue1, 2, messages[0], messages[1]); + VerifyMessageQueueContent(messageQueue2, 0); + + // Case 4: Both queues are non-empty + messageQueue2.Enqueue(*messages[2]); + VerifyMessageQueueContent(messageQueue2, 1, messages[2]); + messageQueue1.EnqueueAllFrom(messageQueue2); + VerifyMessageQueueContent(messageQueue1, 3, messages[0], messages[1], messages[2]); + VerifyMessageQueueContent(messageQueue2, 0); + + // Case 5: Using the same queue as both the target and input queues + messageQueue1.EnqueueAllFrom(messageQueue1); + VerifyMessageQueueContent(messageQueue1, 3, messages[0], messages[1], messages[2]); + messageQueue2.EnqueueAllFrom(messageQueue2); + VerifyMessageQueueContent(messageQueue2, 0); + + testFreeInstance(sInstance); +} + // This function verifies the content of the message queue to match the passed in messages void VerifyMessageQueueContentUsingOtApi(otMessageQueue *aQueue, int aExpectedLength, ...) { @@ -386,6 +439,7 @@ void TestMessageQueueOtApis(void) int main(void) { ot::TestMessageQueue(); + ot::TestMessageQueueEnqueueAllFrom(); ot::TestMessageQueueOtApis(); printf("All tests passed\n"); return 0;