blob: 97926091d5788997af99ba32a196d64d03666763 [file] [log] [blame]
/*
* Copyright (c) 2016, 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 Thread's Network Diagnostic processing.
*/
#include "network_diagnostic.hpp"
#include "coap/coap_message.hpp"
#include "common/array.hpp"
#include "common/as_core_type.hpp"
#include "common/code_utils.hpp"
#include "common/debug.hpp"
#include "common/encoding.hpp"
#include "common/instance.hpp"
#include "common/locator_getters.hpp"
#include "common/log.hpp"
#include "common/random.hpp"
#include "mac/mac.hpp"
#include "net/netif.hpp"
#include "thread/mesh_forwarder.hpp"
#include "thread/mle_router.hpp"
#include "thread/thread_netif.hpp"
#include "thread/thread_tlvs.hpp"
#include "thread/version.hpp"
namespace ot {
RegisterLogModule("NetDiag");
namespace NetworkDiagnostic {
const char Server::kVendorName[] = OPENTHREAD_CONFIG_NET_DIAG_VENDOR_NAME;
const char Server::kVendorModel[] = OPENTHREAD_CONFIG_NET_DIAG_VENDOR_MODEL;
const char Server::kVendorSwVersion[] = OPENTHREAD_CONFIG_NET_DIAG_VENDOR_SW_VERSION;
//---------------------------------------------------------------------------------------------------------------------
// Server
Server::Server(Instance &aInstance)
: InstanceLocator(aInstance)
{
static_assert(sizeof(kVendorName) <= sizeof(VendorNameTlv::StringType), "VENDOR_NAME is too long");
static_assert(sizeof(kVendorModel) <= sizeof(VendorModelTlv::StringType), "VENDOR_MODEL is too long");
static_assert(sizeof(kVendorSwVersion) <= sizeof(VendorSwVersionTlv::StringType), "VENDOR_SW_VERSION is too long");
#if OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE
memcpy(mVendorName, kVendorName, sizeof(kVendorName));
memcpy(mVendorModel, kVendorModel, sizeof(kVendorModel));
memcpy(mVendorSwVersion, kVendorSwVersion, sizeof(kVendorSwVersion));
#endif
}
#if OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE
Error Server::SetVendorName(const char *aVendorName)
{
return SetVendorString(mVendorName, sizeof(mVendorName), aVendorName);
}
Error Server::SetVendorModel(const char *aVendorModel)
{
return SetVendorString(mVendorModel, sizeof(mVendorModel), aVendorModel);
}
Error Server::SetVendorSwVersion(const char *aVendorSwVersion)
{
return SetVendorString(mVendorSwVersion, sizeof(mVendorSwVersion), aVendorSwVersion);
}
Error Server::SetVendorString(char *aDestString, uint16_t kMaxSize, const char *aSrcString)
{
Error error = kErrorInvalidArgs;
uint16_t length;
VerifyOrExit(aSrcString != nullptr);
length = StringLength(aSrcString, kMaxSize);
VerifyOrExit(length < kMaxSize);
VerifyOrExit(IsValidUtf8String(aSrcString));
memcpy(aDestString, aSrcString, length + 1);
error = kErrorNone;
exit:
return error;
}
#endif // OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE
void Server::PrepareMessageInfoForDest(const Ip6::Address &aDestination, Tmf::MessageInfo &aMessageInfo) const
{
if (aDestination.IsMulticast())
{
aMessageInfo.SetMulticastLoop(true);
}
if (aDestination.IsLinkLocal() || aDestination.IsLinkLocalMulticast())
{
aMessageInfo.SetSockAddr(Get<Mle::MleRouter>().GetLinkLocalAddress());
}
else
{
aMessageInfo.SetSockAddrToRloc();
}
aMessageInfo.SetPeerAddr(aDestination);
}
Error Server::AppendIp6AddressList(Message &aMessage)
{
Error error = kErrorNone;
uint16_t count = 0;
for (const Ip6::Netif::UnicastAddress &addr : Get<ThreadNetif>().GetUnicastAddresses())
{
OT_UNUSED_VARIABLE(addr);
count++;
}
if (count * Ip6::Address::kSize <= Tlv::kBaseTlvMaxLength)
{
Tlv tlv;
tlv.SetType(Tlv::kIp6AddressList);
tlv.SetLength(static_cast<uint8_t>(count * Ip6::Address::kSize));
SuccessOrExit(error = aMessage.Append(tlv));
}
else
{
ExtendedTlv extTlv;
extTlv.SetType(Tlv::kIp6AddressList);
extTlv.SetLength(count * Ip6::Address::kSize);
SuccessOrExit(error = aMessage.Append(extTlv));
}
for (const Ip6::Netif::UnicastAddress &addr : Get<ThreadNetif>().GetUnicastAddresses())
{
SuccessOrExit(error = aMessage.Append(addr.GetAddress()));
}
exit:
return error;
}
#if OPENTHREAD_FTD
Error Server::AppendChildTable(Message &aMessage)
{
Error error = kErrorNone;
uint16_t count;
VerifyOrExit(Get<Mle::MleRouter>().IsRouterOrLeader());
count = Min(Get<ChildTable>().GetNumChildren(Child::kInStateValid), kMaxChildEntries);
if (count * sizeof(ChildTableEntry) <= Tlv::kBaseTlvMaxLength)
{
Tlv tlv;
tlv.SetType(Tlv::kChildTable);
tlv.SetLength(static_cast<uint8_t>(count * sizeof(ChildTableEntry)));
SuccessOrExit(error = aMessage.Append(tlv));
}
else
{
ExtendedTlv extTlv;
extTlv.SetType(Tlv::kChildTable);
extTlv.SetLength(count * sizeof(ChildTableEntry));
SuccessOrExit(error = aMessage.Append(extTlv));
}
for (Child &child : Get<ChildTable>().Iterate(Child::kInStateValid))
{
uint8_t timeout = 0;
ChildTableEntry entry;
VerifyOrExit(count--);
while (static_cast<uint32_t>(1 << timeout) < child.GetTimeout())
{
timeout++;
}
entry.Clear();
entry.SetTimeout(timeout + 4);
entry.SetLinkQuality(child.GetLinkQualityIn());
entry.SetChildId(Mle::ChildIdFromRloc16(child.GetRloc16()));
entry.SetMode(child.GetDeviceMode());
SuccessOrExit(error = aMessage.Append(entry));
}
exit:
return error;
}
#endif // OPENTHREAD_FTD
Error Server::AppendMacCounters(Message &aMessage)
{
MacCountersTlv tlv;
const otMacCounters &counters = Get<Mac::Mac>().GetCounters();
memset(&tlv, 0, sizeof(tlv));
tlv.Init();
tlv.SetIfInUnknownProtos(counters.mRxOther);
tlv.SetIfInErrors(counters.mRxErrNoFrame + counters.mRxErrUnknownNeighbor + counters.mRxErrInvalidSrcAddr +
counters.mRxErrSec + counters.mRxErrFcs + counters.mRxErrOther);
tlv.SetIfOutErrors(counters.mTxErrCca);
tlv.SetIfInUcastPkts(counters.mRxUnicast);
tlv.SetIfInBroadcastPkts(counters.mRxBroadcast);
tlv.SetIfInDiscards(counters.mRxAddressFiltered + counters.mRxDestAddrFiltered + counters.mRxDuplicated);
tlv.SetIfOutUcastPkts(counters.mTxUnicast);
tlv.SetIfOutBroadcastPkts(counters.mTxBroadcast);
tlv.SetIfOutDiscards(counters.mTxErrBusyChannel);
return tlv.AppendTo(aMessage);
}
Error Server::AppendRequestedTlvs(const Message &aRequest, Message &aResponse)
{
Error error;
uint16_t offset;
uint16_t endOffset;
SuccessOrExit(error = Tlv::FindTlvValueStartEndOffsets(aRequest, Tlv::kTypeList, offset, endOffset));
for (; offset < endOffset; offset++)
{
uint8_t tlvType;
SuccessOrExit(error = aRequest.Read(offset, tlvType));
SuccessOrExit(error = AppendDiagTlv(tlvType, aResponse));
}
exit:
return error;
}
Error Server::AppendDiagTlv(uint8_t aTlvType, Message &aMessage)
{
Error error = kErrorNone;
switch (aTlvType)
{
case Tlv::kExtMacAddress:
error = Tlv::Append<ExtMacAddressTlv>(aMessage, Get<Mac::Mac>().GetExtAddress());
break;
case Tlv::kAddress16:
error = Tlv::Append<Address16Tlv>(aMessage, Get<Mle::MleRouter>().GetRloc16());
break;
case Tlv::kMode:
error = Tlv::Append<ModeTlv>(aMessage, Get<Mle::MleRouter>().GetDeviceMode().Get());
break;
case Tlv::kVersion:
error = Tlv::Append<VersionTlv>(aMessage, kThreadVersion);
break;
case Tlv::kTimeout:
VerifyOrExit(!Get<Mle::MleRouter>().IsRxOnWhenIdle());
error = Tlv::Append<TimeoutTlv>(aMessage, Get<Mle::MleRouter>().GetTimeout());
break;
case Tlv::kLeaderData:
{
LeaderDataTlv tlv;
tlv.Init();
tlv.Set(Get<Mle::MleRouter>().GetLeaderData());
error = tlv.AppendTo(aMessage);
break;
}
case Tlv::kNetworkData:
error = Tlv::Append<NetworkDataTlv>(aMessage, Get<NetworkData::Leader>().GetBytes(),
Get<NetworkData::Leader>().GetLength());
break;
case Tlv::kIp6AddressList:
error = AppendIp6AddressList(aMessage);
break;
case Tlv::kMacCounters:
error = AppendMacCounters(aMessage);
break;
case Tlv::kVendorName:
error = Tlv::Append<VendorNameTlv>(aMessage, GetVendorName());
break;
case Tlv::kVendorModel:
error = Tlv::Append<VendorModelTlv>(aMessage, GetVendorModel());
break;
case Tlv::kVendorSwVersion:
error = Tlv::Append<VendorSwVersionTlv>(aMessage, GetVendorSwVersion());
break;
case Tlv::kThreadStackVersion:
error = Tlv::Append<ThreadStackVersionTlv>(aMessage, otGetVersionString());
break;
case Tlv::kChannelPages:
{
ChannelPagesTlv tlv;
uint8_t length = 0;
tlv.Init();
for (uint8_t page = 0; page < static_cast<uint8_t>(sizeof(Radio::kSupportedChannelPages) * CHAR_BIT); page++)
{
if (Radio::kSupportedChannelPages & (1 << page))
{
tlv.GetChannelPages()[length++] = page;
}
}
tlv.SetLength(length);
error = tlv.AppendTo(aMessage);
break;
}
#if OPENTHREAD_FTD
case Tlv::kConnectivity:
{
ConnectivityTlv tlv;
tlv.Init();
Get<Mle::MleRouter>().FillConnectivityTlv(tlv);
error = tlv.AppendTo(aMessage);
break;
}
case Tlv::kRoute:
{
RouteTlv tlv;
tlv.Init();
Get<RouterTable>().FillRouteTlv(tlv);
SuccessOrExit(error = tlv.AppendTo(aMessage));
break;
}
case Tlv::kChildTable:
error = AppendChildTable(aMessage);
break;
case Tlv::kMaxChildTimeout:
{
uint32_t maxTimeout;
SuccessOrExit(Get<Mle::MleRouter>().GetMaxChildTimeout(maxTimeout));
error = Tlv::Append<MaxChildTimeoutTlv>(aMessage, maxTimeout);
break;
}
#endif // OPENTHREAD_FTD
default:
break;
}
exit:
return error;
}
template <>
void Server::HandleTmf<kUriDiagnosticGetQuery>(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
VerifyOrExit(aMessage.IsPostRequest());
LogInfo("Received %s from %s", UriToString<kUriDiagnosticGetQuery>(),
aMessageInfo.GetPeerAddr().ToString().AsCString());
// DIAG_GET.qry may be sent as a confirmable request.
if (aMessage.IsConfirmable())
{
IgnoreError(Get<Tmf::Agent>().SendEmptyAck(aMessage, aMessageInfo));
}
#if OPENTHREAD_MTD
SendAnswer(aMessageInfo.GetPeerAddr(), aMessage);
#elif OPENTHREAD_FTD
PrepareAndSendAnswers(aMessageInfo.GetPeerAddr(), aMessage);
#endif
exit:
return;
}
#if OPENTHREAD_MTD
void Server::SendAnswer(const Ip6::Address &aDestination, const Message &aRequest)
{
Error error = kErrorNone;
Coap::Message *answer = nullptr;
Tmf::MessageInfo messageInfo(GetInstance());
AnswerTlv answerTlv;
uint16_t queryId;
answer = Get<Tmf::Agent>().NewConfirmablePostMessage(kUriDiagnosticGetAnswer);
VerifyOrExit(answer != nullptr, error = kErrorNoBufs);
IgnoreError(answer->SetPriority(aRequest.GetPriority()));
if (Tlv::Find<QueryIdTlv>(aRequest, queryId) == kErrorNone)
{
SuccessOrExit(error = Tlv::Append<QueryIdTlv>(*answer, queryId));
}
SuccessOrExit(error = AppendRequestedTlvs(aRequest, *answer));
answerTlv.Init(0, /* aIsLast */ true);
SuccessOrExit(answer->Append(answerTlv));
PrepareMessageInfoForDest(aDestination, messageInfo);
error = Get<Tmf::Agent>().SendMessage(*answer, messageInfo);
exit:
FreeMessageOnError(answer, error);
}
#endif // OPENTHREAD_MTD
#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>().NewConfirmablePostMessage(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
{
// Indicates whether `aAnswer` is the last one associated with
// the same query.
bool isLast = true;
AnswerTlv answerTlv;
// If there is no Answer TLV, we assume it is the last answer.
SuccessOrExit(Tlv::FindTlv(aAnswer, answerTlv));
isLast = answerTlv.IsLast();
exit:
return isLast;
}
void Server::FreeAllRelatedAnswers(Coap::Message &aFirstAnswer)
{
// This method dequeues and frees all answer messages related to
// same query as `aFirstAnswer`. Note that related answers are
// enqueued in order.
Coap::Message *answer = &aFirstAnswer;
while (answer != nullptr)
{
Coap::Message *next = IsLastAnswer(*answer) ? nullptr : answer->GetNextCoapMessage();
mAnswerQueue.DequeueAndFree(*answer);
answer = next;
}
}
void Server::PrepareAndSendAnswers(const Ip6::Address &aDestination, const Message &aRequest)
{
Coap::Message *answer;
Error error;
AnswerInfo info;
uint16_t offset;
uint16_t length;
uint16_t endOffset;
AnswerTlv answerTlv;
if (Tlv::Find<QueryIdTlv>(aRequest, info.mQueryId) == kErrorNone)
{
info.mHasQueryId = true;
}
info.mPriority = aRequest.GetPriority();
SuccessOrExit(error = AllocateAnswer(answer, info));
SuccessOrExit(error = Tlv::FindTlvValueOffset(aRequest, Tlv::kTypeList, offset, length));
endOffset = offset + length;
for (; offset < endOffset; offset++)
{
uint8_t tlvType;
SuccessOrExit(error = aRequest.Read(offset, tlvType));
switch (tlvType)
{
case ChildTlv::kType:
SuccessOrExit(error = AppendChildTableAsChildTlvs(answer, info));
break;
case ChildIp6AddressListTlv::kType:
SuccessOrExit(error = AppendChildTableIp6AddressList(answer, info));
break;
case RouterNeighborTlv::kType:
SuccessOrExit(error = AppendRouterNeighborTlvs(answer, info));
break;
default:
SuccessOrExit(error = AppendDiagTlv(tlvType, *answer));
break;
}
SuccessOrExit(error = CheckAnswerLength(answer, info));
}
answerTlv.Init(info.mAnswerIndex, /* aIsLast */ true);
SuccessOrExit(error = answer->Append(answerTlv));
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;
AnswerTlv answerTlv;
VerifyOrExit(aAnswer->GetLength() >= kAnswerMessageLengthThreshold);
answerTlv.Init(aInfo.mAnswerIndex++, /* aIsLast */ false);
SuccessOrExit(error = aAnswer->Append(answerTlv));
error = AllocateAnswer(aAnswer, aInfo);
exit:
return error;
}
void Server::SendNextAnswer(Coap::Message &aAnswer, const Ip6::Address &aDestination)
{
// This method send the given next `aAnswer` associated with
// a query to the `aDestination`.
Error error = kErrorNone;
Coap::Message *nextAnswer = IsLastAnswer(aAnswer) ? nullptr : aAnswer.GetNextCoapMessage();
Tmf::MessageInfo messageInfo(GetInstance());
mAnswerQueue.Dequeue(aAnswer);
PrepareMessageInfoForDest(aDestination, messageInfo);
// When sending the message, we pass `nextAnswer` as `aContext`
// to be used when invoking callback `HandleAnswerResponse()`.
error = Get<Tmf::Agent>().SendMessage(aAnswer, messageInfo, HandleAnswerResponse, nextAnswer);
if (error != kErrorNone)
{
// If the `SendMessage()` fails, we `Free` the dequeued
// `aAnswer` and all the related next answers in the queue.
aAnswer.Free();
if (nextAnswer != nullptr)
{
FreeAllRelatedAnswers(*nextAnswer);
}
}
}
void Server::HandleAnswerResponse(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo, Error aResult)
{
Coap::Message *nextAnswer = static_cast<Coap::Message *>(aContext);
VerifyOrExit(nextAnswer != nullptr);
nextAnswer->Get<Server>().HandleAnswerResponse(*nextAnswer, AsCoapMessagePtr(aMessage), AsCoreTypePtr(aMessageInfo),
aResult);
exit:
return;
}
void Server::HandleAnswerResponse(Coap::Message &aNextAnswer,
Coap::Message *aResponse,
const Ip6::MessageInfo *aMessageInfo,
Error aResult)
{
Error error = aResult;
SuccessOrExit(error);
VerifyOrExit(aResponse != nullptr && aMessageInfo != nullptr, error = kErrorDrop);
VerifyOrExit(aResponse->GetCode() == Coap::kCodeChanged, error = kErrorDrop);
SendNextAnswer(aNextAnswer, aMessageInfo->GetPeerAddr());
exit:
if (error != kErrorNone)
{
FreeAllRelatedAnswers(aNextAnswer);
}
}
Error Server::AppendChildTableAsChildTlvs(Coap::Message *&aAnswer, AnswerInfo &aInfo)
{
Error error = kErrorNone;
ChildTlv childTlv;
for (Child &child : Get<ChildTable>().Iterate(Child::kInStateValid))
{
childTlv.InitFrom(child);
SuccessOrExit(error = childTlv.AppendTo(*aAnswer));
SuccessOrExit(error = CheckAnswerLength(aAnswer, aInfo));
}
// Add empty TLV to indicate end of the list
childTlv.InitAsEmpty();
SuccessOrExit(error = childTlv.AppendTo(*aAnswer));
exit:
return error;
}
Error Server::AppendRouterNeighborTlvs(Coap::Message *&aAnswer, AnswerInfo &aInfo)
{
Error error = kErrorNone;
RouterNeighborTlv neighborTlv;
for (Router &router : Get<RouterTable>())
{
if (!router.IsStateValid())
{
continue;
}
neighborTlv.InitFrom(router);
SuccessOrExit(error = neighborTlv.AppendTo(*aAnswer));
SuccessOrExit(error = CheckAnswerLength(aAnswer, aInfo));
}
// Add empty TLV to indicate end of the list
neighborTlv.InitAsEmpty();
SuccessOrExit(error = neighborTlv.AppendTo(*aAnswer));
exit:
return error;
}
Error Server::AppendChildTableIp6AddressList(Coap::Message *&aAnswer, AnswerInfo &aInfo)
{
Error error = kErrorNone;
Tlv tlv;
for (const Child &child : Get<ChildTable>().Iterate(Child::kInStateValid))
{
SuccessOrExit(error = AppendChildIp6AddressListTlv(*aAnswer, child));
SuccessOrExit(error = CheckAnswerLength(aAnswer, aInfo));
}
// Add empty TLV to indicate end of the list
tlv.SetType(Tlv::kChildIp6AddressList);
tlv.SetLength(0);
SuccessOrExit(error = aAnswer->Append(tlv));
exit:
return error;
}
Error Server::AppendChildIp6AddressListTlv(Coap::Message &aAnswer, const Child &aChild)
{
Error error = kErrorNone;
uint16_t numIp6Addr = 0;
ChildIp6AddressListTlvValue tlvValue;
for (const Ip6::Address &address : aChild.IterateIp6Addresses())
{
OT_UNUSED_VARIABLE(address);
numIp6Addr++;
}
VerifyOrExit(numIp6Addr > 0);
if ((numIp6Addr * sizeof(Ip6::Address) + sizeof(ChildIp6AddressListTlvValue)) <= Tlv::kBaseTlvMaxLength)
{
Tlv tlv;
tlv.SetType(Tlv::kChildIp6AddressList);
tlv.SetLength(static_cast<uint8_t>(numIp6Addr * sizeof(Ip6::Address) + sizeof(ChildIp6AddressListTlvValue)));
SuccessOrExit(error = aAnswer.Append(tlv));
}
else
{
ExtendedTlv extTlv;
extTlv.SetType(Tlv::kChildIp6AddressList);
extTlv.SetLength(numIp6Addr * sizeof(Ip6::Address) + sizeof(ChildIp6AddressListTlvValue));
SuccessOrExit(error = aAnswer.Append(extTlv));
}
tlvValue.SetRloc16(aChild.GetRloc16());
SuccessOrExit(error = aAnswer.Append(tlvValue));
for (const Ip6::Address &address : aChild.IterateIp6Addresses())
{
SuccessOrExit(error = aAnswer.Append(address));
}
exit:
return error;
}
#endif // OPENTHREAD_FTD
template <>
void Server::HandleTmf<kUriDiagnosticGetRequest>(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
Error error = kErrorNone;
Coap::Message *response = nullptr;
VerifyOrExit(aMessage.IsConfirmablePostRequest(), error = kErrorDrop);
LogInfo("Received %s from %s", UriToString<kUriDiagnosticGetRequest>(),
aMessageInfo.GetPeerAddr().ToString().AsCString());
response = Get<Tmf::Agent>().NewResponseMessage(aMessage);
VerifyOrExit(response != nullptr, error = kErrorNoBufs);
IgnoreError(response->SetPriority(aMessage.GetPriority()));
SuccessOrExit(error = AppendRequestedTlvs(aMessage, *response));
SuccessOrExit(error = Get<Tmf::Agent>().SendMessage(*response, aMessageInfo));
exit:
FreeMessageOnError(response, error);
}
template <> void Server::HandleTmf<kUriDiagnosticReset>(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
uint16_t offset = 0;
uint8_t type;
Tlv tlv;
VerifyOrExit(aMessage.IsConfirmablePostRequest());
LogInfo("Received %s from %s", UriToString<kUriDiagnosticReset>(),
aMessageInfo.GetPeerAddr().ToString().AsCString());
SuccessOrExit(aMessage.Read(aMessage.GetOffset(), tlv));
VerifyOrExit(tlv.GetType() == Tlv::kTypeList);
offset = aMessage.GetOffset() + sizeof(Tlv);
for (uint8_t i = 0; i < tlv.GetLength(); i++)
{
SuccessOrExit(aMessage.Read(offset + i, type));
switch (type)
{
case Tlv::kMacCounters:
Get<Mac::Mac>().ResetCounters();
break;
case Tlv::kMleCounters:
Get<Mle::Mle>().ResetCounters();
break;
default:
break;
}
}
IgnoreError(Get<Tmf::Agent>().SendEmptyAck(aMessage, aMessageInfo));
exit:
return;
}
#if OPENTHREAD_CONFIG_TMF_NETDIAG_CLIENT_ENABLE
//---------------------------------------------------------------------------------------------------------------------
// Client
Client::Client(Instance &aInstance)
: InstanceLocator(aInstance)
, mQueryId(Random::NonCrypto::GetUint16())
{
}
Error Client::SendDiagnosticGet(const Ip6::Address &aDestination,
const uint8_t aTlvTypes[],
uint8_t aCount,
GetCallback aCallback,
void *aContext)
{
Error error;
if (aDestination.IsMulticast())
{
error = SendCommand(kUriDiagnosticGetQuery, Message::kPriorityNormal, aDestination, aTlvTypes, aCount);
}
else
{
error = SendCommand(kUriDiagnosticGetRequest, Message::kPriorityNormal, aDestination, aTlvTypes, aCount,
&HandleGetResponse, this);
}
SuccessOrExit(error);
mGetCallback.Set(aCallback, aContext);
exit:
return error;
}
Error Client::SendCommand(Uri aUri,
Message::Priority aPriority,
const Ip6::Address &aDestination,
const uint8_t aTlvTypes[],
uint8_t aCount,
Coap::ResponseHandler aHandler,
void *aContext)
{
Error error;
Coap::Message *message = nullptr;
Tmf::MessageInfo messageInfo(GetInstance());
switch (aUri)
{
case kUriDiagnosticGetQuery:
message = Get<Tmf::Agent>().NewNonConfirmablePostMessage(aUri);
break;
case kUriDiagnosticGetRequest:
case kUriDiagnosticReset:
message = Get<Tmf::Agent>().NewConfirmablePostMessage(aUri);
break;
default:
OT_ASSERT(false);
}
VerifyOrExit(message != nullptr, error = kErrorNoBufs);
IgnoreError(message->SetPriority(aPriority));
if (aCount > 0)
{
SuccessOrExit(error = Tlv::Append<TypeListTlv>(*message, aTlvTypes, aCount));
}
if (aUri == kUriDiagnosticGetQuery)
{
SuccessOrExit(error = Tlv::Append<QueryIdTlv>(*message, ++mQueryId));
}
Get<Server>().PrepareMessageInfoForDest(aDestination, messageInfo);
SuccessOrExit(error = Get<Tmf::Agent>().SendMessage(*message, messageInfo, aHandler, aContext));
LogInfo("Sent %s to %s", UriToString(aUri), aDestination.ToString().AsCString());
exit:
FreeMessageOnError(message, error);
return error;
}
void Client::HandleGetResponse(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo, Error aResult)
{
static_cast<Client *>(aContext)->HandleGetResponse(AsCoapMessagePtr(aMessage), AsCoreTypePtr(aMessageInfo),
aResult);
}
void Client::HandleGetResponse(Coap::Message *aMessage, const Ip6::MessageInfo *aMessageInfo, Error aResult)
{
SuccessOrExit(aResult);
VerifyOrExit(aMessage->GetCode() == Coap::kCodeChanged, aResult = kErrorFailed);
exit:
mGetCallback.InvokeIfSet(aResult, aMessage, aMessageInfo);
}
template <>
void Client::HandleTmf<kUriDiagnosticGetAnswer>(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
VerifyOrExit(aMessage.IsConfirmablePostRequest());
LogInfo("Received %s from %s", ot::UriToString<kUriDiagnosticGetAnswer>(),
aMessageInfo.GetPeerAddr().ToString().AsCString());
#if OPENTHREAD_CONFIG_MESH_DIAG_ENABLE && OPENTHREAD_FTD
// Let the `MeshDiag` process the message first.
if (!Get<Utils::MeshDiag>().HandleDiagnosticGetAnswer(aMessage, aMessageInfo))
#endif
{
mGetCallback.InvokeIfSet(kErrorNone, &aMessage, &aMessageInfo);
}
IgnoreError(Get<Tmf::Agent>().SendEmptyAck(aMessage, aMessageInfo));
exit:
return;
}
Error Client::SendDiagnosticReset(const Ip6::Address &aDestination, const uint8_t aTlvTypes[], uint8_t aCount)
{
return SendCommand(kUriDiagnosticReset, Message::kPriorityNormal, aDestination, aTlvTypes, aCount);
}
static void ParseRoute(const RouteTlv &aRouteTlv, otNetworkDiagRoute &aNetworkDiagRoute)
{
uint8_t routeCount = 0;
for (uint8_t i = 0; i <= Mle::kMaxRouterId; ++i)
{
if (!aRouteTlv.IsRouterIdSet(i))
{
continue;
}
aNetworkDiagRoute.mRouteData[routeCount].mRouterId = i;
aNetworkDiagRoute.mRouteData[routeCount].mRouteCost = aRouteTlv.GetRouteCost(routeCount);
aNetworkDiagRoute.mRouteData[routeCount].mLinkQualityIn = aRouteTlv.GetLinkQualityIn(routeCount);
aNetworkDiagRoute.mRouteData[routeCount].mLinkQualityOut = aRouteTlv.GetLinkQualityOut(routeCount);
++routeCount;
}
aNetworkDiagRoute.mRouteCount = routeCount;
aNetworkDiagRoute.mIdSequence = aRouteTlv.GetRouterIdSequence();
}
static inline void ParseMacCounters(const MacCountersTlv &aMacCountersTlv, otNetworkDiagMacCounters &aMacCounters)
{
aMacCounters.mIfInUnknownProtos = aMacCountersTlv.GetIfInUnknownProtos();
aMacCounters.mIfInErrors = aMacCountersTlv.GetIfInErrors();
aMacCounters.mIfOutErrors = aMacCountersTlv.GetIfOutErrors();
aMacCounters.mIfInUcastPkts = aMacCountersTlv.GetIfInUcastPkts();
aMacCounters.mIfInBroadcastPkts = aMacCountersTlv.GetIfInBroadcastPkts();
aMacCounters.mIfInDiscards = aMacCountersTlv.GetIfInDiscards();
aMacCounters.mIfOutUcastPkts = aMacCountersTlv.GetIfOutUcastPkts();
aMacCounters.mIfOutBroadcastPkts = aMacCountersTlv.GetIfOutBroadcastPkts();
aMacCounters.mIfOutDiscards = aMacCountersTlv.GetIfOutDiscards();
}
Error Client::GetNextDiagTlv(const Coap::Message &aMessage, Iterator &aIterator, TlvInfo &aTlvInfo)
{
Error error;
uint16_t offset = (aIterator == 0) ? aMessage.GetOffset() : aIterator;
while (offset < aMessage.GetLength())
{
bool skipTlv = false;
uint16_t valueOffset;
uint16_t tlvLength;
union
{
Tlv tlv;
ExtendedTlv extTlv;
};
SuccessOrExit(error = aMessage.Read(offset, tlv));
if (tlv.IsExtended())
{
SuccessOrExit(error = aMessage.Read(offset, extTlv));
valueOffset = offset + sizeof(ExtendedTlv);
tlvLength = extTlv.GetLength();
}
else
{
valueOffset = offset + sizeof(Tlv);
tlvLength = tlv.GetLength();
}
VerifyOrExit(offset + tlv.GetSize() <= aMessage.GetLength(), error = kErrorParse);
switch (tlv.GetType())
{
case Tlv::kExtMacAddress:
SuccessOrExit(error =
Tlv::Read<ExtMacAddressTlv>(aMessage, offset, AsCoreType(&aTlvInfo.mData.mExtAddress)));
break;
case Tlv::kAddress16:
SuccessOrExit(error = Tlv::Read<Address16Tlv>(aMessage, offset, aTlvInfo.mData.mAddr16));
break;
case Tlv::kMode:
{
uint8_t mode;
SuccessOrExit(error = Tlv::Read<ModeTlv>(aMessage, offset, mode));
Mle::DeviceMode(mode).Get(aTlvInfo.mData.mMode);
break;
}
case Tlv::kTimeout:
SuccessOrExit(error = Tlv::Read<TimeoutTlv>(aMessage, offset, aTlvInfo.mData.mTimeout));
break;
case Tlv::kConnectivity:
{
ConnectivityTlv connectivityTlv;
VerifyOrExit(!tlv.IsExtended(), error = kErrorParse);
SuccessOrExit(error = aMessage.Read(offset, connectivityTlv));
VerifyOrExit(connectivityTlv.IsValid(), error = kErrorParse);
connectivityTlv.GetConnectivity(aTlvInfo.mData.mConnectivity);
break;
}
case Tlv::kRoute:
{
RouteTlv routeTlv;
uint16_t bytesToRead = static_cast<uint16_t>(Min(tlv.GetSize(), static_cast<uint32_t>(sizeof(routeTlv))));
VerifyOrExit(!tlv.IsExtended(), error = kErrorParse);
SuccessOrExit(error = aMessage.Read(offset, &routeTlv, bytesToRead));
VerifyOrExit(routeTlv.IsValid(), error = kErrorParse);
ParseRoute(routeTlv, aTlvInfo.mData.mRoute);
break;
}
case Tlv::kLeaderData:
{
LeaderDataTlv leaderDataTlv;
VerifyOrExit(!tlv.IsExtended(), error = kErrorParse);
SuccessOrExit(error = aMessage.Read(offset, leaderDataTlv));
VerifyOrExit(leaderDataTlv.IsValid(), error = kErrorParse);
leaderDataTlv.Get(AsCoreType(&aTlvInfo.mData.mLeaderData));
break;
}
case Tlv::kNetworkData:
static_assert(sizeof(aTlvInfo.mData.mNetworkData.m8) >= NetworkData::NetworkData::kMaxSize,
"NetworkData array in `otNetworkDiagTlv` is too small");
VerifyOrExit(tlvLength <= NetworkData::NetworkData::kMaxSize, error = kErrorParse);
aTlvInfo.mData.mNetworkData.mCount = static_cast<uint8_t>(tlvLength);
aMessage.ReadBytes(valueOffset, aTlvInfo.mData.mNetworkData.m8, tlvLength);
break;
case Tlv::kIp6AddressList:
{
uint16_t addrListLength = GetArrayLength(aTlvInfo.mData.mIp6AddrList.mList);
Ip6::Address *addrEntry = AsCoreTypePtr(&aTlvInfo.mData.mIp6AddrList.mList[0]);
uint8_t &addrCount = aTlvInfo.mData.mIp6AddrList.mCount;
VerifyOrExit((tlvLength % Ip6::Address::kSize) == 0, error = kErrorParse);
// `TlvInfo` has a fixed array for IPv6 addresses. If there
// are more addresses in the message, we read and return as
// many as can fit in array and ignore the rest.
addrCount = 0;
while ((tlvLength > 0) && (addrCount < addrListLength))
{
SuccessOrExit(error = aMessage.Read(valueOffset, *addrEntry));
addrCount++;
addrEntry++;
valueOffset += Ip6::Address::kSize;
tlvLength -= Ip6::Address::kSize;
}
break;
}
case Tlv::kMacCounters:
{
MacCountersTlv macCountersTlv;
SuccessOrExit(error = aMessage.Read(offset, macCountersTlv));
VerifyOrExit(macCountersTlv.IsValid(), error = kErrorParse);
ParseMacCounters(macCountersTlv, aTlvInfo.mData.mMacCounters);
break;
}
case Tlv::kMleCounters:
{
MleCountersTlv mleCoutersTlv;
SuccessOrExit(error = aMessage.Read(offset, mleCoutersTlv));
VerifyOrExit(mleCoutersTlv.IsValid(), error = kErrorParse);
mleCoutersTlv.Read(aTlvInfo.mData.mMleCounters);
break;
}
case Tlv::kBatteryLevel:
SuccessOrExit(error = Tlv::Read<BatteryLevelTlv>(aMessage, offset, aTlvInfo.mData.mBatteryLevel));
break;
case Tlv::kSupplyVoltage:
SuccessOrExit(error = Tlv::Read<SupplyVoltageTlv>(aMessage, offset, aTlvInfo.mData.mSupplyVoltage));
break;
case Tlv::kChildTable:
{
uint16_t childInfoLength = GetArrayLength(aTlvInfo.mData.mChildTable.mTable);
ChildInfo *childInfo = &aTlvInfo.mData.mChildTable.mTable[0];
uint8_t &childCount = aTlvInfo.mData.mChildTable.mCount;
VerifyOrExit((tlvLength % sizeof(ChildTableEntry)) == 0, error = kErrorParse);
// `TlvInfo` has a fixed array Child Table entries. If there
// are more entries in the message, we read and return as
// many as can fit in array and ignore the rest.
childCount = 0;
while ((tlvLength > 0) && (childCount < childInfoLength))
{
ChildTableEntry entry;
SuccessOrExit(error = aMessage.Read(valueOffset, entry));
childInfo->mTimeout = entry.GetTimeout();
childInfo->mLinkQuality = entry.GetLinkQuality();
childInfo->mChildId = entry.GetChildId();
entry.GetMode().Get(childInfo->mMode);
childCount++;
childInfo++;
tlvLength -= sizeof(ChildTableEntry);
valueOffset += sizeof(ChildTableEntry);
}
break;
}
case Tlv::kChannelPages:
aTlvInfo.mData.mChannelPages.mCount =
static_cast<uint8_t>(Min(tlvLength, GetArrayLength(aTlvInfo.mData.mChannelPages.m8)));
aMessage.ReadBytes(valueOffset, aTlvInfo.mData.mChannelPages.m8, aTlvInfo.mData.mChannelPages.mCount);
break;
case Tlv::kMaxChildTimeout:
SuccessOrExit(error = Tlv::Read<MaxChildTimeoutTlv>(aMessage, offset, aTlvInfo.mData.mMaxChildTimeout));
break;
case Tlv::kVersion:
SuccessOrExit(error = Tlv::Read<VersionTlv>(aMessage, offset, aTlvInfo.mData.mVersion));
break;
case Tlv::kVendorName:
SuccessOrExit(error = Tlv::Read<VendorNameTlv>(aMessage, offset, aTlvInfo.mData.mVendorName));
break;
case Tlv::kVendorModel:
SuccessOrExit(error = Tlv::Read<VendorModelTlv>(aMessage, offset, aTlvInfo.mData.mVendorModel));
break;
case Tlv::kVendorSwVersion:
SuccessOrExit(error = Tlv::Read<VendorSwVersionTlv>(aMessage, offset, aTlvInfo.mData.mVendorSwVersion));
break;
case Tlv::kThreadStackVersion:
SuccessOrExit(error =
Tlv::Read<ThreadStackVersionTlv>(aMessage, offset, aTlvInfo.mData.mThreadStackVersion));
break;
default:
// Skip unrecognized TLVs.
skipTlv = true;
break;
}
offset += tlv.GetSize();
if (!skipTlv)
{
// Exit if a TLV is recognized and parsed successfully.
aTlvInfo.mType = tlv.GetType();
aIterator = offset;
error = kErrorNone;
ExitNow();
}
}
error = kErrorNotFound;
exit:
return error;
}
#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
const char *Client::UriToString(Uri aUri)
{
const char *str = "";
switch (aUri)
{
case kUriDiagnosticGetQuery:
str = ot::UriToString<kUriDiagnosticGetQuery>();
break;
case kUriDiagnosticGetRequest:
str = ot::UriToString<kUriDiagnosticGetRequest>();
break;
case kUriDiagnosticReset:
str = ot::UriToString<kUriDiagnosticReset>();
break;
default:
break;
}
return str;
}
#endif // #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
#endif // OPENTHREAD_CONFIG_TMF_NETDIAG_CLIENT_ENABLE
} // namespace NetworkDiagnostic
} // namespace ot