blob: 60d78f15c6ce488d0e2c344d6f12c30558a4fd69 [file] [log] [blame]
/*
* Copyright (c) 2023, 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 Mesh Diag module.
*/
#include "mesh_diag.hpp"
#if OPENTHREAD_CONFIG_MESH_DIAG_ENABLE && OPENTHREAD_FTD
#include "common/as_core_type.hpp"
#include "common/code_utils.hpp"
#include "common/debug.hpp"
#include "common/instance.hpp"
#include "common/locator_getters.hpp"
#include "common/log.hpp"
namespace ot {
namespace Utils {
using namespace NetworkDiagnostic;
RegisterLogModule("MeshDiag");
//---------------------------------------------------------------------------------------------------------------------
// MeshDiag
MeshDiag::MeshDiag(Instance &aInstance)
: InstanceLocator(aInstance)
, mState(kStateIdle)
, mExpectedQueryId(0)
, mExpectedAnswerIndex(0)
, mTimer(aInstance)
{
}
Error MeshDiag::DiscoverTopology(const DiscoverConfig &aConfig, DiscoverCallback aCallback, void *aContext)
{
static constexpr uint8_t kMaxTlvsToRequest = 6;
Error error = kErrorNone;
uint8_t tlvs[kMaxTlvsToRequest];
uint8_t tlvsLength = 0;
VerifyOrExit(Get<Mle::Mle>().IsAttached(), error = kErrorInvalidState);
VerifyOrExit(mState == kStateIdle, error = kErrorBusy);
tlvs[tlvsLength++] = Address16Tlv::kType;
tlvs[tlvsLength++] = ExtMacAddressTlv::kType;
tlvs[tlvsLength++] = RouteTlv::kType;
tlvs[tlvsLength++] = VersionTlv::kType;
if (aConfig.mDiscoverIp6Addresses)
{
tlvs[tlvsLength++] = Ip6AddressListTlv::kType;
}
if (aConfig.mDiscoverChildTable)
{
tlvs[tlvsLength++] = ChildTableTlv::kType;
}
Get<RouterTable>().GetRouterIdSet(mDiscover.mExpectedRouterIdSet);
for (uint8_t routerId = 0; routerId <= Mle::kMaxRouterId; routerId++)
{
Ip6::Address destination;
if (!mDiscover.mExpectedRouterIdSet.Contains(routerId))
{
continue;
}
destination = Get<Mle::MleRouter>().GetMeshLocal16();
destination.GetIid().SetLocator(Mle::Rloc16FromRouterId(routerId));
SuccessOrExit(error = Get<Client>().SendCommand(kUriDiagnosticGetRequest, Message::kPriorityLow, destination,
tlvs, tlvsLength, HandleDiagGetResponse, this));
}
mDiscover.mCallback.Set(aCallback, aContext);
mState = kStateDicoverTopology;
mTimer.Start(kResponseTimeout);
exit:
return error;
}
void MeshDiag::HandleDiagGetResponse(void *aContext,
otMessage *aMessage,
const otMessageInfo *aMessageInfo,
Error aResult)
{
static_cast<MeshDiag *>(aContext)->HandleDiagGetResponse(AsCoapMessagePtr(aMessage), AsCoreTypePtr(aMessageInfo),
aResult);
}
void MeshDiag::HandleDiagGetResponse(Coap::Message *aMessage, const Ip6::MessageInfo *aMessageInfo, Error aResult)
{
OT_UNUSED_VARIABLE(aMessageInfo);
Error error;
RouterInfo routerInfo;
Ip6AddrIterator ip6AddrIterator;
ChildIterator childIterator;
SuccessOrExit(aResult);
VerifyOrExit(aMessage != nullptr);
VerifyOrExit(mState == kStateDicoverTopology);
SuccessOrExit(routerInfo.ParseFrom(*aMessage));
if (ip6AddrIterator.InitFrom(*aMessage) == kErrorNone)
{
routerInfo.mIp6AddrIterator = &ip6AddrIterator;
}
if (childIterator.InitFrom(*aMessage, routerInfo.mRloc16) == kErrorNone)
{
routerInfo.mChildIterator = &childIterator;
}
mDiscover.mExpectedRouterIdSet.Remove(routerInfo.mRouterId);
if (mDiscover.mExpectedRouterIdSet.GetNumberOfAllocatedIds() == 0)
{
error = kErrorNone;
mState = kStateIdle;
mTimer.Stop();
}
else
{
error = kErrorPending;
}
mDiscover.mCallback.InvokeIfSet(error, &routerInfo);
exit:
return;
}
Error MeshDiag::SendQuery(uint16_t aRloc16, const uint8_t *aTlvs, uint8_t aTlvsLength)
{
Error error = kErrorNone;
Ip6::Address destination;
VerifyOrExit(Get<Mle::Mle>().IsAttached(), error = kErrorInvalidState);
VerifyOrExit(mState == kStateIdle, error = kErrorBusy);
VerifyOrExit(Mle::IsActiveRouter(aRloc16), error = kErrorInvalidArgs);
VerifyOrExit(Get<RouterTable>().IsAllocated(Mle::RouterIdFromRloc16(aRloc16)), error = kErrorNotFound);
destination = Get<Mle::MleRouter>().GetMeshLocal16();
destination.GetIid().SetLocator(aRloc16);
SuccessOrExit(error = Get<Client>().SendCommand(kUriDiagnosticGetQuery, Message::kPriorityNormal, destination,
aTlvs, aTlvsLength));
mExpectedQueryId = Get<Client>().GetLastQueryId();
mExpectedAnswerIndex = 0;
mTimer.Start(kResponseTimeout);
exit:
return error;
}
Error MeshDiag::QueryChildTable(uint16_t aRloc16, QueryChildTableCallback aCallback, void *aContext)
{
static const uint8_t kTlvTypes[] = {ChildTlv::kType};
Error error;
SuccessOrExit(error = SendQuery(aRloc16, kTlvTypes, sizeof(kTlvTypes)));
mQueryChildTable.mCallback.Set(aCallback, aContext);
mQueryChildTable.mRouterRloc16 = aRloc16;
mState = kStateQueryChildTable;
exit:
return error;
}
Error MeshDiag::QueryChildrenIp6Addrs(uint16_t aRloc16, ChildIp6AddrsCallback aCallback, void *aContext)
{
static const uint8_t kTlvTypes[] = {ChildIp6AddressListTlv::kType};
Error error;
SuccessOrExit(error = SendQuery(aRloc16, kTlvTypes, sizeof(kTlvTypes)));
mQueryChildrenIp6Addrs.mCallback.Set(aCallback, aContext);
mQueryChildrenIp6Addrs.mParentRloc16 = aRloc16;
mState = kStateQueryChildrenIp6Addrs;
exit:
return error;
}
Error MeshDiag::QueryRouterNeighborTable(uint16_t aRloc16, RouterNeighborTableCallback aCallback, void *aContext)
{
static const uint8_t kTlvTypes[] = {RouterNeighborTlv::kType};
Error error;
SuccessOrExit(error = SendQuery(aRloc16, kTlvTypes, sizeof(kTlvTypes)));
mQueryRouterNeighborTable.mCallback.Set(aCallback, aContext);
mQueryRouterNeighborTable.mRouterRloc16 = aRloc16;
mState = kStateQueryRouterNeighborTable;
exit:
return error;
}
bool MeshDiag::HandleDiagnosticGetAnswer(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
bool didPorcess = false;
switch (mState)
{
case kStateQueryChildTable:
didPorcess = ProcessChildTableAnswer(aMessage, aMessageInfo);
break;
case kStateQueryChildrenIp6Addrs:
didPorcess = ProcessChildrenIp6AddrsAnswer(aMessage, aMessageInfo);
break;
case kStateQueryRouterNeighborTable:
didPorcess = ProcessRouterNeighborTableAnswer(aMessage, aMessageInfo);
break;
default:
break;
}
return didPorcess;
}
Error MeshDiag::ProcessMessage(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo, uint16_t aSenderRloc16)
{
// This method processes the received answer message to
// check whether it is from the intended sender and matches
// the expected query ID and answer index.
Error error = kErrorFailed;
AnswerTlv answerTlv;
uint16_t queryId;
VerifyOrExit(Get<Mle::Mle>().IsRoutingLocator(aMessageInfo.GetPeerAddr()));
VerifyOrExit(aMessageInfo.GetPeerAddr().GetIid().GetLocator() == aSenderRloc16);
SuccessOrExit(Tlv::Find<QueryIdTlv>(aMessage, queryId));
VerifyOrExit(queryId == mExpectedQueryId);
SuccessOrExit(Tlv::FindTlv(aMessage, answerTlv));
if (answerTlv.GetIndex() != mExpectedAnswerIndex)
{
Finalize(kErrorResponseTimeout);
ExitNow();
}
mExpectedAnswerIndex++;
error = kErrorNone;
exit:
return error;
}
bool MeshDiag::ProcessChildTableAnswer(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
bool didPorcess = false;
ChildTlv childTlv;
ChildEntry entry;
uint16_t offset;
SuccessOrExit(ProcessMessage(aMessage, aMessageInfo, mQueryChildTable.mRouterRloc16));
while (true)
{
SuccessOrExit(Tlv::FindTlv(aMessage, childTlv, offset));
VerifyOrExit(!childTlv.IsExtended());
didPorcess = true;
if (childTlv.GetLength() == 0)
{
// We reached end of the list.
mState = kStateIdle;
mTimer.Stop();
mQueryChildTable.mCallback.InvokeIfSet(kErrorNone, nullptr);
ExitNow();
}
VerifyOrExit(childTlv.GetLength() >= sizeof(ChildTlv) - sizeof(Tlv));
IgnoreError(aMessage.Read(offset, childTlv));
entry.SetFrom(childTlv);
mQueryChildTable.mCallback.InvokeIfSet(kErrorPending, &entry);
// Make sure query operation is not canceled from the
// callback.
VerifyOrExit(mState == kStateQueryChildTable);
aMessage.SetOffset(static_cast<uint16_t>(offset + childTlv.GetSize()));
}
exit:
return didPorcess;
}
bool MeshDiag::ProcessRouterNeighborTableAnswer(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
bool didPorcess = false;
RouterNeighborTlv neighborTlv;
RouterNeighborEntry entry;
uint16_t offset;
SuccessOrExit(ProcessMessage(aMessage, aMessageInfo, mQueryRouterNeighborTable.mRouterRloc16));
while (true)
{
SuccessOrExit(Tlv::FindTlv(aMessage, neighborTlv, offset));
VerifyOrExit(!neighborTlv.IsExtended());
didPorcess = true;
if (neighborTlv.GetLength() == 0)
{
// We reached end of the list.
mState = kStateIdle;
mTimer.Stop();
mQueryRouterNeighborTable.mCallback.InvokeIfSet(kErrorNone, nullptr);
ExitNow();
}
VerifyOrExit(neighborTlv.GetLength() >= sizeof(RouterNeighborTlv) - sizeof(Tlv));
entry.SetFrom(neighborTlv);
mQueryRouterNeighborTable.mCallback.InvokeIfSet(kErrorPending, &entry);
// Make sure query operation is not canceled from the
// callback.
VerifyOrExit(mState == kStateQueryRouterNeighborTable);
aMessage.SetOffset(static_cast<uint16_t>(offset + neighborTlv.GetSize()));
}
exit:
return didPorcess;
}
bool MeshDiag::ProcessChildrenIp6AddrsAnswer(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
bool didPorcess = false;
uint16_t offset;
uint16_t endOffset;
ChildIp6AddressListTlvValue tlvValue;
Ip6AddrIterator ip6AddrIterator;
SuccessOrExit(ProcessMessage(aMessage, aMessageInfo, mQueryChildrenIp6Addrs.mParentRloc16));
while (true)
{
SuccessOrExit(Tlv::FindTlvValueStartEndOffsets(aMessage, ChildIp6AddressListTlv::kType, offset, endOffset));
didPorcess = true;
if (offset == endOffset)
{
// We reached end of the list
mState = kStateIdle;
mTimer.Stop();
mQueryChildrenIp6Addrs.mCallback.InvokeIfSet(kErrorNone, Mle::kInvalidRloc16, nullptr);
ExitNow();
}
// Read the `ChildIp6AddressListTlvValue` (which contains the
// child RLOC16) and then prepare the `Ip6AddrIterator`.
VerifyOrExit(offset + sizeof(tlvValue) <= endOffset);
IgnoreError(aMessage.Read(offset, tlvValue));
offset += sizeof(tlvValue);
ip6AddrIterator.mMessage = &aMessage;
ip6AddrIterator.mCurOffset = offset;
ip6AddrIterator.mEndOffset = endOffset;
mQueryChildrenIp6Addrs.mCallback.InvokeIfSet(kErrorPending, tlvValue.GetRloc16(), &ip6AddrIterator);
// Make sure query operation is not canceled from the
// callback.
VerifyOrExit(mState == kStateQueryChildrenIp6Addrs);
aMessage.SetOffset(endOffset);
}
exit:
return didPorcess;
}
void MeshDiag::Cancel(void)
{
switch (mState)
{
case kStateIdle:
case kStateQueryChildTable:
case kStateQueryChildrenIp6Addrs:
case kStateQueryRouterNeighborTable:
break;
case kStateDicoverTopology:
IgnoreError(Get<Tmf::Agent>().AbortTransaction(HandleDiagGetResponse, this));
break;
}
mState = kStateIdle;
mTimer.Stop();
}
void MeshDiag::Finalize(Error aError)
{
// Finalize an ongoing query operation (if any) invoking
// the corresponding callback with `aError`.
State oldState = mState;
Cancel();
switch (oldState)
{
case kStateIdle:
break;
case kStateDicoverTopology:
mDiscover.mCallback.InvokeIfSet(aError, nullptr);
break;
case kStateQueryChildTable:
mQueryChildTable.mCallback.InvokeIfSet(aError, nullptr);
break;
case kStateQueryChildrenIp6Addrs:
mQueryChildrenIp6Addrs.mCallback.InvokeIfSet(aError, Mle::kInvalidRloc16, nullptr);
break;
case kStateQueryRouterNeighborTable:
mQueryRouterNeighborTable.mCallback.InvokeIfSet(aError, nullptr);
break;
}
}
void MeshDiag::HandleTimer(void) { Finalize(kErrorResponseTimeout); }
//---------------------------------------------------------------------------------------------------------------------
// MeshDiag::RouterInfo
Error MeshDiag::RouterInfo::ParseFrom(const Message &aMessage)
{
Error error = kErrorNone;
Mle::Mle &mle = aMessage.Get<Mle::Mle>();
RouteTlv routeTlv;
Clear();
SuccessOrExit(error = Tlv::Find<Address16Tlv>(aMessage, mRloc16));
SuccessOrExit(error = Tlv::Find<ExtMacAddressTlv>(aMessage, AsCoreType(&mExtAddress)));
SuccessOrExit(error = Tlv::FindTlv(aMessage, routeTlv));
switch (error = Tlv::Find<VersionTlv>(aMessage, mVersion))
{
case kErrorNone:
break;
case kErrorNotFound:
mVersion = kVersionUnknown;
error = kErrorNone;
break;
default:
ExitNow();
}
mRouterId = Mle::RouterIdFromRloc16(mRloc16);
mIsThisDevice = (mRloc16 == mle.GetRloc16());
mIsThisDeviceParent = mle.IsChild() && (mRloc16 == mle.GetParent().GetRloc16());
mIsLeader = (mRouterId == mle.GetLeaderId());
mIsBorderRouter = aMessage.Get<NetworkData::Leader>().ContainsBorderRouterWithRloc(mRloc16);
for (uint8_t id = 0, index = 0; id <= Mle::kMaxRouterId; id++)
{
if (routeTlv.IsRouterIdSet(id))
{
mLinkQualities[id] = routeTlv.GetLinkQualityIn(index);
index++;
}
}
exit:
return error;
}
//---------------------------------------------------------------------------------------------------------------------
// MeshDiag::Ip6AddrIterator
Error MeshDiag::Ip6AddrIterator::InitFrom(const Message &aMessage)
{
Error error;
SuccessOrExit(error = Tlv::FindTlvValueStartEndOffsets(aMessage, Ip6AddressListTlv::kType, mCurOffset, mEndOffset));
mMessage = &aMessage;
exit:
return error;
}
Error MeshDiag::Ip6AddrIterator::GetNextAddress(Ip6::Address &aAddress)
{
Error error = kErrorNone;
VerifyOrExit(mMessage != nullptr, error = kErrorNotFound);
VerifyOrExit(mCurOffset + sizeof(Ip6::Address) <= mEndOffset, error = kErrorNotFound);
IgnoreError(mMessage->Read(mCurOffset, aAddress));
mCurOffset += sizeof(Ip6::Address);
exit:
return error;
}
//---------------------------------------------------------------------------------------------------------------------
// MeshDiag::ChildIterator
Error MeshDiag::ChildIterator::InitFrom(const Message &aMessage, uint16_t aParentRloc16)
{
Error error;
SuccessOrExit(error = Tlv::FindTlvValueStartEndOffsets(aMessage, ChildTableTlv::kType, mCurOffset, mEndOffset));
mMessage = &aMessage;
mParentRloc16 = aParentRloc16;
exit:
return error;
}
Error MeshDiag::ChildIterator::GetNextChildInfo(ChildInfo &aChildInfo)
{
Error error = kErrorNone;
ChildTableEntry entry;
VerifyOrExit(mMessage != nullptr, error = kErrorNotFound);
VerifyOrExit(mCurOffset + sizeof(ChildTableEntry) <= mEndOffset, error = kErrorNotFound);
IgnoreError(mMessage->Read(mCurOffset, entry));
mCurOffset += sizeof(ChildTableEntry);
aChildInfo.mRloc16 = mParentRloc16 + entry.GetChildId();
entry.GetMode().Get(aChildInfo.mMode);
aChildInfo.mLinkQuality = entry.GetLinkQuality();
aChildInfo.mIsThisDevice = (aChildInfo.mRloc16 == mMessage->Get<Mle::Mle>().GetRloc16());
aChildInfo.mIsBorderRouter = mMessage->Get<NetworkData::Leader>().ContainsBorderRouterWithRloc(aChildInfo.mRloc16);
exit:
return error;
}
//---------------------------------------------------------------------------------------------------------------------
// MeshDiag::ChildEntry
void MeshDiag::ChildEntry::SetFrom(const ChildTlv &aChildTlv)
{
mRxOnWhenIdle = (aChildTlv.GetFlags() & ChildTlv::kFlagsRxOnWhenIdle);
mDeviceTypeFtd = (aChildTlv.GetFlags() & ChildTlv::kFlagsFtd);
mFullNetData = (aChildTlv.GetFlags() & ChildTlv::kFlagsFullNetdta);
mCslSynchronized = (aChildTlv.GetFlags() & ChildTlv::kFlagsCslSync);
mSupportsErrRate = (aChildTlv.GetFlags() & ChildTlv::kFlagsTrackErrRate);
mRloc16 = aChildTlv.GetRloc16();
mExtAddress = aChildTlv.GetExtAddress();
mVersion = aChildTlv.GetVersion();
mTimeout = aChildTlv.GetTimeout();
mAge = aChildTlv.GetAge();
mConnectionTime = aChildTlv.GetConnectionTime();
mSupervisionInterval = aChildTlv.GetSupervisionInterval();
mLinkMargin = aChildTlv.GetLinkMargin();
mAverageRssi = aChildTlv.GetAverageRssi();
mLastRssi = aChildTlv.GetLastRssi();
mFrameErrorRate = aChildTlv.GetFrameErrorRate();
mMessageErrorRate = aChildTlv.GetMessageErrorRate();
mQueuedMessageCount = aChildTlv.GetQueuedMessageCount();
mCslPeriod = aChildTlv.GetCslPeriod();
mCslTimeout = aChildTlv.GetCslTimeout();
mCslChannel = aChildTlv.GetCslChannel();
}
//---------------------------------------------------------------------------------------------------------------------
// MeshDiag::RouterNeighborEntry
void MeshDiag::RouterNeighborEntry::SetFrom(const RouterNeighborTlv &aTlv)
{
mSupportsErrRate = (aTlv.GetFlags() & RouterNeighborTlv::kFlagsTrackErrRate);
mRloc16 = aTlv.GetRloc16();
mExtAddress = aTlv.GetExtAddress();
mVersion = aTlv.GetVersion();
mConnectionTime = aTlv.GetConnectionTime();
mLinkMargin = aTlv.GetLinkMargin();
mAverageRssi = aTlv.GetAverageRssi();
mLastRssi = aTlv.GetLastRssi();
mFrameErrorRate = aTlv.GetFrameErrorRate();
mMessageErrorRate = aTlv.GetMessageErrorRate();
}
} // namespace Utils
} // namespace ot
#endif // #if OPENTHREAD_CONFIG_MESH_DIAG_ENABLE && OPENTHREAD_FTD