mirror of
https://github.com/espressif/openthread.git
synced 2026-09-12 04:00:10 +00:00
[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.
This commit is contained in:
@@ -755,6 +755,8 @@ openthread_core_files = [
|
|||||||
"thread/network_diagnostic.hpp",
|
"thread/network_diagnostic.hpp",
|
||||||
"thread/network_diagnostic_tlvs.cpp",
|
"thread/network_diagnostic_tlvs.cpp",
|
||||||
"thread/network_diagnostic_tlvs.hpp",
|
"thread/network_diagnostic_tlvs.hpp",
|
||||||
|
"thread/network_diagnostic_types.cpp",
|
||||||
|
"thread/network_diagnostic_types.hpp",
|
||||||
"thread/panid_query_server.cpp",
|
"thread/panid_query_server.cpp",
|
||||||
"thread/panid_query_server.hpp",
|
"thread/panid_query_server.hpp",
|
||||||
"thread/peer.cpp",
|
"thread/peer.cpp",
|
||||||
|
|||||||
@@ -268,6 +268,7 @@ set(COMMON_SOURCES
|
|||||||
thread/network_data_types.cpp
|
thread/network_data_types.cpp
|
||||||
thread/network_diagnostic.cpp
|
thread/network_diagnostic.cpp
|
||||||
thread/network_diagnostic_tlvs.cpp
|
thread/network_diagnostic_tlvs.cpp
|
||||||
|
thread/network_diagnostic_types.cpp
|
||||||
thread/panid_query_server.cpp
|
thread/panid_query_server.cpp
|
||||||
thread/peer.cpp
|
thread/peer.cpp
|
||||||
thread/peer_table.cpp
|
thread/peer_table.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::Iterator MessageQueue::begin(void) { return Message::Iterator(GetHead()); }
|
||||||
|
|
||||||
Message::ConstIterator MessageQueue::begin(void) const { return Message::ConstIterator(GetHead()); }
|
Message::ConstIterator MessageQueue::begin(void) const { return Message::ConstIterator(GetHead()); }
|
||||||
|
|||||||
@@ -1731,6 +1731,15 @@ public:
|
|||||||
*/
|
*/
|
||||||
void DequeueAndFreeAll(void);
|
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.
|
* Gets the information about number of messages and buffers in the queue.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -539,34 +539,6 @@ exit:
|
|||||||
|
|
||||||
#if OPENTHREAD_FTD
|
#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<Tmf::Agent>().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<QueryIdTlv>(*aAnswer, aInfo.mQueryId));
|
|
||||||
}
|
|
||||||
|
|
||||||
exit:
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Server::IsLastAnswer(const Coap::Message &aAnswer) const
|
bool Server::IsLastAnswer(const Coap::Message &aAnswer) const
|
||||||
{
|
{
|
||||||
// Indicates whether `aAnswer` is the last one associated with
|
// 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;
|
Error error;
|
||||||
AnswerInfo info;
|
|
||||||
uint8_t tlvType;
|
uint8_t tlvType;
|
||||||
TlvTypeListIterator iterator;
|
TlvTypeListIterator iterator;
|
||||||
AnswerTlvValue answerTlvValue;
|
|
||||||
|
|
||||||
if (Tlv::Find<QueryIdTlv>(aRequest, info.mQueryId) == kErrorNone)
|
SuccessOrExit(error = aAnswerBuilder.Start(aRequest));
|
||||||
{
|
|
||||||
info.mHasQueryId = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
info.mPriority = aRequest.GetPriority();
|
|
||||||
|
|
||||||
SuccessOrExit(error = AllocateAnswer(answer, info));
|
|
||||||
|
|
||||||
SuccessOrExit(error = iterator.InitForTypeListTlv(aRequest));
|
SuccessOrExit(error = iterator.InitForTypeListTlv(aRequest));
|
||||||
|
|
||||||
@@ -626,51 +604,23 @@ void Server::PrepareAndSendAnswers(const Ip6::Address &aDestination, const Messa
|
|||||||
switch (tlvType)
|
switch (tlvType)
|
||||||
{
|
{
|
||||||
case ChildTlv::kType:
|
case ChildTlv::kType:
|
||||||
SuccessOrExit(error = AppendChildTableAsChildTlvs(answer, info));
|
SuccessOrExit(error = AppendChildTableAsChildTlvs(aAnswerBuilder));
|
||||||
break;
|
break;
|
||||||
case ChildIp6AddressListTlv::kType:
|
case ChildIp6AddressListTlv::kType:
|
||||||
SuccessOrExit(error = AppendChildTableIp6AddressList(answer, info));
|
SuccessOrExit(error = AppendChildTableIp6AddressList(aAnswerBuilder));
|
||||||
break;
|
break;
|
||||||
case RouterNeighborTlv::kType:
|
case RouterNeighborTlv::kType:
|
||||||
SuccessOrExit(error = AppendRouterNeighborTlvs(answer, info));
|
SuccessOrExit(error = AppendRouterNeighborTlvs(aAnswerBuilder));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
SuccessOrExit(error = AppendDiagTlv(tlvType, *answer));
|
SuccessOrExit(error = AppendDiagTlv(tlvType, aAnswerBuilder.GetAnswer()));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
SuccessOrExit(error = CheckAnswerLength(answer, info));
|
SuccessOrExit(error = aAnswerBuilder.CheckAnswerLength());
|
||||||
}
|
}
|
||||||
|
|
||||||
answerTlvValue.Init(info.mAnswerIndex, AnswerTlvValue::kIsLast);
|
SuccessOrExit(error = aAnswerBuilder.Finish());
|
||||||
SuccessOrExit(error = Tlv::Append<AnswerTlv>(*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<AnswerTlv>(*aAnswer, answerTlvValue));
|
|
||||||
|
|
||||||
error = AllocateAnswer(aAnswer, aInfo);
|
|
||||||
|
|
||||||
exit:
|
exit:
|
||||||
return error;
|
return error;
|
||||||
@@ -734,7 +684,7 @@ exit:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Error Server::AppendChildTableAsChildTlvs(Coap::Message *&aAnswer, AnswerInfo &aInfo)
|
Error Server::AppendChildTableAsChildTlvs(AnswerBuilder &aAnswerBuilder)
|
||||||
{
|
{
|
||||||
Error error = kErrorNone;
|
Error error = kErrorNone;
|
||||||
ChildTlvValue childTlvValue;
|
ChildTlvValue childTlvValue;
|
||||||
@@ -743,17 +693,17 @@ Error Server::AppendChildTableAsChildTlvs(Coap::Message *&aAnswer, AnswerInfo &a
|
|||||||
{
|
{
|
||||||
childTlvValue.InitFrom(child);
|
childTlvValue.InitFrom(child);
|
||||||
|
|
||||||
SuccessOrExit(error = Tlv::Append<ChildTlv>(*aAnswer, childTlvValue));
|
SuccessOrExit(error = Tlv::Append<ChildTlv>(aAnswerBuilder.GetAnswer(), childTlvValue));
|
||||||
SuccessOrExit(error = CheckAnswerLength(aAnswer, aInfo));
|
SuccessOrExit(error = aAnswerBuilder.CheckAnswerLength());
|
||||||
}
|
}
|
||||||
|
|
||||||
error = Tlv::AppendEmpty<ChildTlv>(*aAnswer);
|
error = Tlv::AppendEmpty<ChildTlv>(aAnswerBuilder.GetAnswer());
|
||||||
|
|
||||||
exit:
|
exit:
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
Error Server::AppendRouterNeighborTlvs(Coap::Message *&aAnswer, AnswerInfo &aInfo)
|
Error Server::AppendRouterNeighborTlvs(AnswerBuilder &aAnswerBuilder)
|
||||||
{
|
{
|
||||||
Error error = kErrorNone;
|
Error error = kErrorNone;
|
||||||
RouterNeighborTlvValue neighborTlvValue;
|
RouterNeighborTlvValue neighborTlvValue;
|
||||||
@@ -767,27 +717,27 @@ Error Server::AppendRouterNeighborTlvs(Coap::Message *&aAnswer, AnswerInfo &aInf
|
|||||||
|
|
||||||
neighborTlvValue.InitFrom(router);
|
neighborTlvValue.InitFrom(router);
|
||||||
|
|
||||||
SuccessOrExit(error = Tlv::Append<RouterNeighborTlv>(*aAnswer, neighborTlvValue));
|
SuccessOrExit(error = Tlv::Append<RouterNeighborTlv>(aAnswerBuilder.GetAnswer(), neighborTlvValue));
|
||||||
SuccessOrExit(error = CheckAnswerLength(aAnswer, aInfo));
|
SuccessOrExit(error = aAnswerBuilder.CheckAnswerLength());
|
||||||
}
|
}
|
||||||
|
|
||||||
error = Tlv::AppendEmpty<RouterNeighborTlv>(*aAnswer);
|
error = Tlv::AppendEmpty<RouterNeighborTlv>(aAnswerBuilder.GetAnswer());
|
||||||
|
|
||||||
exit:
|
exit:
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
Error Server::AppendChildTableIp6AddressList(Coap::Message *&aAnswer, AnswerInfo &aInfo)
|
Error Server::AppendChildTableIp6AddressList(AnswerBuilder &aAnswerBuilder)
|
||||||
{
|
{
|
||||||
Error error = kErrorNone;
|
Error error = kErrorNone;
|
||||||
|
|
||||||
for (const Child &child : Get<ChildTable>().Iterate(Child::kInStateValid))
|
for (const Child &child : Get<ChildTable>().Iterate(Child::kInStateValid))
|
||||||
{
|
{
|
||||||
SuccessOrExit(error = AppendChildIp6AddressListTlv(*aAnswer, child));
|
SuccessOrExit(error = AppendChildIp6AddressListTlv(aAnswerBuilder.GetAnswer(), child));
|
||||||
SuccessOrExit(error = CheckAnswerLength(aAnswer, aInfo));
|
SuccessOrExit(error = aAnswerBuilder.CheckAnswerLength());
|
||||||
}
|
}
|
||||||
|
|
||||||
error = Tlv::AppendEmpty<ChildIp6AddressListTlv>(*aAnswer);
|
error = Tlv::AppendEmpty<ChildIp6AddressListTlv>(aAnswerBuilder.GetAnswer());
|
||||||
|
|
||||||
exit:
|
exit:
|
||||||
return error;
|
return error;
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
#include "common/numeric_limits.hpp"
|
#include "common/numeric_limits.hpp"
|
||||||
#include "net/udp6.hpp"
|
#include "net/udp6.hpp"
|
||||||
#include "thread/network_diagnostic_tlvs.hpp"
|
#include "thread/network_diagnostic_tlvs.hpp"
|
||||||
|
#include "thread/network_diagnostic_types.hpp"
|
||||||
#include "thread/tmf.hpp"
|
#include "thread/tmf.hpp"
|
||||||
#include "thread/uri_paths.hpp"
|
#include "thread/uri_paths.hpp"
|
||||||
|
|
||||||
@@ -119,8 +120,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr uint16_t kMaxChildEntries = 398;
|
static constexpr uint16_t kMaxChildEntries = 398;
|
||||||
static constexpr uint16_t kAnswerMessageLengthThreshold = 800;
|
|
||||||
|
|
||||||
class TlvTypeListIterator
|
class TlvTypeListIterator
|
||||||
{
|
{
|
||||||
@@ -139,25 +139,6 @@ private:
|
|||||||
BitSet<NumericLimits<uint8_t>::kMax + 1> mProcessedTlvs;
|
BitSet<NumericLimits<uint8_t>::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 AppendDiagTlv(uint8_t aTlvType, Message &aMessage);
|
||||||
Error AppendIp6AddressList(Message &aMessage);
|
Error AppendIp6AddressList(Message &aMessage);
|
||||||
Error AppendRequestedTlvs(const Message &aRequest, Message &aResponse);
|
Error AppendRequestedTlvs(const Message &aRequest, Message &aResponse);
|
||||||
@@ -169,16 +150,15 @@ private:
|
|||||||
#if OPENTHREAD_MTD
|
#if OPENTHREAD_MTD
|
||||||
void SendAnswer(const Ip6::Address &aDestination, const Message &aRequest);
|
void SendAnswer(const Ip6::Address &aDestination, const Message &aRequest);
|
||||||
#elif OPENTHREAD_FTD
|
#elif OPENTHREAD_FTD
|
||||||
Error AllocateAnswer(Coap::Message *&aAnswer, AnswerInfo &aInfo);
|
|
||||||
Error CheckAnswerLength(Coap::Message *&aAnswer, AnswerInfo &aInfo);
|
|
||||||
bool IsLastAnswer(const Coap::Message &aAnswer) const;
|
bool IsLastAnswer(const Coap::Message &aAnswer) const;
|
||||||
void FreeAllRelatedAnswers(Coap::Message &aFirstAnswer);
|
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);
|
void SendNextAnswer(Coap::Message &aAnswer, const Ip6::Address &aDestination);
|
||||||
Error AppendChildTable(Message &aMessage);
|
Error AppendChildTable(Message &aMessage);
|
||||||
Error AppendChildTableAsChildTlvs(Coap::Message *&aAnswer, AnswerInfo &aInfo);
|
Error AppendChildTableAsChildTlvs(AnswerBuilder &aAnswerBuilder);
|
||||||
Error AppendRouterNeighborTlvs(Coap::Message *&aAnswer, AnswerInfo &aInfo);
|
Error AppendRouterNeighborTlvs(AnswerBuilder &aAnswerBuilder);
|
||||||
Error AppendChildTableIp6AddressList(Coap::Message *&aAnswer, AnswerInfo &aInfo);
|
Error AppendChildTableIp6AddressList(AnswerBuilder &aAnswerBuilder);
|
||||||
Error AppendChildIp6AddressListTlv(Message &aAnswer, const Child &aChild);
|
Error AppendChildIp6AddressListTlv(Message &aAnswer, const Child &aChild);
|
||||||
Error AppendEnhancedRoute(Message &aMessage);
|
Error AppendEnhancedRoute(Message &aMessage);
|
||||||
|
|
||||||
|
|||||||
@@ -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<QueryIdTlv>(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<Tmf::Agent>().AllocateAndInitConfirmablePostMessage(kUriDiagnosticGetAnswer);
|
||||||
|
VerifyOrExit(mAnswer != nullptr, error = kErrorNoBufs);
|
||||||
|
|
||||||
|
IgnoreError(mAnswer->SetPriority(mPriority));
|
||||||
|
|
||||||
|
mAnswers.Enqueue(*mAnswer);
|
||||||
|
|
||||||
|
if (mHasQueryId)
|
||||||
|
{
|
||||||
|
SuccessOrExit(error = Tlv::Append<QueryIdTlv>(*mAnswer, mQueryId));
|
||||||
|
}
|
||||||
|
|
||||||
|
exit:
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
Error AnswerBuilder::AppendAnswerTlv(AnswerTlvValue::IsLastFlag aFlag)
|
||||||
|
{
|
||||||
|
AnswerTlvValue value;
|
||||||
|
|
||||||
|
value.Init(mAnswerIndex++, aFlag);
|
||||||
|
|
||||||
|
return Tlv::Append<AnswerTlv>(*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
|
||||||
@@ -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_
|
||||||
@@ -286,6 +286,59 @@ void TestMessageQueue(void)
|
|||||||
testFreeInstance(sInstance);
|
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<MessagePool>();
|
||||||
|
|
||||||
|
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
|
// This function verifies the content of the message queue to match the passed in messages
|
||||||
void VerifyMessageQueueContentUsingOtApi(otMessageQueue *aQueue, int aExpectedLength, ...)
|
void VerifyMessageQueueContentUsingOtApi(otMessageQueue *aQueue, int aExpectedLength, ...)
|
||||||
{
|
{
|
||||||
@@ -386,6 +439,7 @@ void TestMessageQueueOtApis(void)
|
|||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
ot::TestMessageQueue();
|
ot::TestMessageQueue();
|
||||||
|
ot::TestMessageQueueEnqueueAllFrom();
|
||||||
ot::TestMessageQueueOtApis();
|
ot::TestMessageQueueOtApis();
|
||||||
printf("All tests passed\n");
|
printf("All tests passed\n");
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user