blob: 9866225ae0adcc412ec944bd1668b1e107bc6776 [file]
/*
* Copyright (c) 2020, 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.
*/
#define OTBR_LOG_TAG "UTILS"
#include "utils/thread_helper.hpp"
#include <assert.h>
#include <limits.h>
#include <string.h>
#include <time.h>
#include <openthread/border_agent.h>
#include <openthread/border_router.h>
#include <openthread/channel_manager.h>
#include <openthread/dataset_ftd.h>
#if OTBR_ENABLE_DNSSD_DISCOVERY_PROXY
#include <openthread/dnssd_server.h>
#endif
#include <openthread/jam_detection.h>
#include <openthread/joiner.h>
#if OTBR_ENABLE_NAT64
#include <openthread/crypto.h>
#include <openthread/nat64.h>
#include "utils/sha256.hpp"
#endif
#if OTBR_ENABLE_DHCP6_PD
#include "utils/sha256.hpp"
#endif
#if OTBR_ENABLE_LINK_METRICS_TELEMETRY
#include <openthread/link_metrics.h>
#endif
#if OTBR_ENABLE_SRP_ADVERTISING_PROXY
#include <openthread/srp_server.h>
#endif
#include <openthread/thread_ftd.h>
#if OTBR_ENABLE_TREL
#include <openthread/trel.h>
#endif
#include <net/if.h>
#include <openthread/platform/radio.h>
#include "common/byteswap.hpp"
#include "common/code_utils.hpp"
#include "common/logging.hpp"
#include "common/tlv.hpp"
#include "host/rcp_host.hpp"
namespace otbr {
namespace agent {
namespace {
const Tlv *FindTlv(uint8_t aTlvType, const uint8_t *aTlvs, int aTlvsSize)
{
const Tlv *result = nullptr;
for (const Tlv *tlv = reinterpret_cast<const Tlv *>(aTlvs);
reinterpret_cast<const uint8_t *>(tlv) + sizeof(Tlv) < aTlvs + aTlvsSize; tlv = tlv->GetNext())
{
if (tlv->GetType() == aTlvType)
{
ExitNow(result = tlv);
}
}
exit:
return result;
}
#if OTBR_ENABLE_TELEMETRY_DATA_API
static uint32_t TelemetryNodeTypeFromRoleAndLinkMode(const otDeviceRole &aRole, const otLinkModeConfig &aLinkModeCfg)
{
uint32_t nodeType;
switch (aRole)
{
case OT_DEVICE_ROLE_DISABLED:
nodeType = threadnetwork::TelemetryData::NODE_TYPE_DISABLED;
break;
case OT_DEVICE_ROLE_DETACHED:
nodeType = threadnetwork::TelemetryData::NODE_TYPE_DETACHED;
break;
case OT_DEVICE_ROLE_ROUTER:
nodeType = threadnetwork::TelemetryData::NODE_TYPE_ROUTER;
break;
case OT_DEVICE_ROLE_LEADER:
nodeType = threadnetwork::TelemetryData::NODE_TYPE_LEADER;
break;
case OT_DEVICE_ROLE_CHILD:
if (!aLinkModeCfg.mRxOnWhenIdle)
{
nodeType = threadnetwork::TelemetryData::NODE_TYPE_SLEEPY_END;
}
else if (!aLinkModeCfg.mDeviceType)
{
// If it's not an FTD, return as minimal end device.
nodeType = threadnetwork::TelemetryData::NODE_TYPE_MINIMAL_END;
}
else
{
nodeType = threadnetwork::TelemetryData::NODE_TYPE_END;
}
break;
default:
nodeType = threadnetwork::TelemetryData::NODE_TYPE_UNSPECIFIED;
}
return nodeType;
}
#if OTBR_ENABLE_SRP_ADVERTISING_PROXY
threadnetwork::TelemetryData_SrpServerState SrpServerStateFromOtSrpServerState(otSrpServerState srpServerState)
{
switch (srpServerState)
{
case OT_SRP_SERVER_STATE_DISABLED:
return threadnetwork::TelemetryData::SRP_SERVER_STATE_DISABLED;
case OT_SRP_SERVER_STATE_RUNNING:
return threadnetwork::TelemetryData::SRP_SERVER_STATE_RUNNING;
case OT_SRP_SERVER_STATE_STOPPED:
return threadnetwork::TelemetryData::SRP_SERVER_STATE_STOPPED;
default:
return threadnetwork::TelemetryData::SRP_SERVER_STATE_UNSPECIFIED;
}
}
threadnetwork::TelemetryData_SrpServerAddressMode SrpServerAddressModeFromOtSrpServerAddressMode(
otSrpServerAddressMode srpServerAddressMode)
{
switch (srpServerAddressMode)
{
case OT_SRP_SERVER_ADDRESS_MODE_ANYCAST:
return threadnetwork::TelemetryData::SRP_SERVER_ADDRESS_MODE_STATE_ANYCAST;
case OT_SRP_SERVER_ADDRESS_MODE_UNICAST:
return threadnetwork::TelemetryData::SRP_SERVER_ADDRESS_MODE_UNICAST;
default:
return threadnetwork::TelemetryData::SRP_SERVER_ADDRESS_MODE_UNSPECIFIED;
}
}
#endif // OTBR_ENABLE_SRP_ADVERTISING_PROXY
#if OTBR_ENABLE_NAT64
threadnetwork::TelemetryData_Nat64State Nat64StateFromOtNat64State(otNat64State nat64State)
{
switch (nat64State)
{
case OT_NAT64_STATE_DISABLED:
return threadnetwork::TelemetryData::NAT64_STATE_DISABLED;
case OT_NAT64_STATE_NOT_RUNNING:
return threadnetwork::TelemetryData::NAT64_STATE_NOT_RUNNING;
case OT_NAT64_STATE_IDLE:
return threadnetwork::TelemetryData::NAT64_STATE_IDLE;
case OT_NAT64_STATE_ACTIVE:
return threadnetwork::TelemetryData::NAT64_STATE_ACTIVE;
default:
return threadnetwork::TelemetryData::NAT64_STATE_UNSPECIFIED;
}
}
void CopyNat64TrafficCounters(const otNat64Counters &from, threadnetwork::TelemetryData_Nat64TrafficCounters *to)
{
to->set_ipv4_to_ipv6_packets(from.m4To6Packets);
to->set_ipv4_to_ipv6_bytes(from.m4To6Bytes);
to->set_ipv6_to_ipv4_packets(from.m6To4Packets);
to->set_ipv6_to_ipv4_bytes(from.m6To4Bytes);
}
#endif // OTBR_ENABLE_NAT64
#if OTBR_ENABLE_DHCP6_PD
threadnetwork::TelemetryData_Dhcp6PdState Dhcp6PdStateFromOtDhcp6PdState(otBorderRoutingDhcp6PdState dhcp6PdState)
{
threadnetwork::TelemetryData_Dhcp6PdState pdState = threadnetwork::TelemetryData::DHCP6_PD_STATE_UNSPECIFIED;
switch (dhcp6PdState)
{
case OT_BORDER_ROUTING_DHCP6_PD_STATE_DISABLED:
pdState = threadnetwork::TelemetryData::DHCP6_PD_STATE_DISABLED;
break;
case OT_BORDER_ROUTING_DHCP6_PD_STATE_STOPPED:
pdState = threadnetwork::TelemetryData::DHCP6_PD_STATE_STOPPED;
break;
case OT_BORDER_ROUTING_DHCP6_PD_STATE_RUNNING:
pdState = threadnetwork::TelemetryData::DHCP6_PD_STATE_RUNNING;
break;
default:
break;
}
return pdState;
}
#endif // OTBR_ENABLE_DHCP6_PD
void CopyMdnsResponseCounters(const MdnsResponseCounters &from, threadnetwork::TelemetryData_MdnsResponseCounters *to)
{
to->set_success_count(from.mSuccess);
to->set_not_found_count(from.mNotFound);
to->set_invalid_args_count(from.mInvalidArgs);
to->set_duplicated_count(from.mDuplicated);
to->set_not_implemented_count(from.mNotImplemented);
to->set_unknown_error_count(from.mUnknownError);
to->set_aborted_count(from.mAborted);
to->set_invalid_state_count(from.mInvalidState);
}
#endif // OTBR_ENABLE_TELEMETRY_DATA_API
} // namespace
ThreadHelper::ThreadHelper(otInstance *aInstance, otbr::Host::RcpHost *aHost)
: mInstance(aInstance)
, mHost(aHost)
{
#if OTBR_ENABLE_TELEMETRY_DATA_API && (OTBR_ENABLE_NAT64 || OTBR_ENABLE_DHCP6_PD)
otError error;
SuccessOrExit(error = otPlatCryptoRandomGet(mNat64PdCommonSalt, sizeof(mNat64PdCommonSalt)));
exit:
if (error != OT_ERROR_NONE)
{
otbrLogWarning("Error otPlatCryptoRandomGet: %s", otThreadErrorToString(error));
}
#endif
}
void ThreadHelper::StateChangedCallback(otChangedFlags aFlags)
{
if (aFlags & OT_CHANGED_THREAD_ROLE)
{
otDeviceRole role = mHost->GetDeviceRole();
for (const auto &handler : mDeviceRoleHandlers)
{
handler(role);
}
if (role != OT_DEVICE_ROLE_DISABLED && role != OT_DEVICE_ROLE_DETACHED)
{
if (mAttachHandler != nullptr)
{
if (mWaitingMgmtSetResponse)
{
otbrLogInfo("StateChangedCallback is called during waiting for Mgmt Set Response");
ExitNow();
}
if (mAttachPendingDatasetTlvs.mLength == 0)
{
AttachHandler handler = mAttachHandler;
mAttachHandler = nullptr;
handler(OT_ERROR_NONE, mAttachDelayMs);
}
else
{
otOperationalDataset emptyDataset = {};
otError error =
otDatasetSendMgmtPendingSet(mInstance, &emptyDataset, mAttachPendingDatasetTlvs.mTlvs,
mAttachPendingDatasetTlvs.mLength, MgmtSetResponseHandler, this);
if (error != OT_ERROR_NONE)
{
AttachHandler handler = mAttachHandler;
mAttachHandler = nullptr;
mAttachPendingDatasetTlvs = {};
mWaitingMgmtSetResponse = false;
handler(error, 0);
}
else
{
mWaitingMgmtSetResponse = true;
}
}
}
else if (mJoinerHandler != nullptr)
{
mJoinerHandler(OT_ERROR_NONE);
mJoinerHandler = nullptr;
}
}
}
if (aFlags & OT_CHANGED_ACTIVE_DATASET)
{
ActiveDatasetChangedCallback();
}
exit:
return;
}
void ThreadHelper::ActiveDatasetChangedCallback()
{
otError error;
otOperationalDatasetTlvs datasetTlvs;
SuccessOrExit(error = otDatasetGetActiveTlvs(mInstance, &datasetTlvs));
for (const auto &handler : mActiveDatasetChangeHandlers)
{
handler(datasetTlvs);
}
exit:
if (error != OT_ERROR_NONE)
{
otbrLogWarning("Error handling active dataset change: %s", otThreadErrorToString(error));
}
}
#if OTBR_ENABLE_DBUS_SERVER
void ThreadHelper::OnUpdateMeshCopTxt(std::map<std::string, std::vector<uint8_t>> aUpdate)
{
if (mUpdateMeshCopTxtHandler)
{
mUpdateMeshCopTxtHandler(std::move(aUpdate));
}
else
{
otbrLogErr("No UpdateMeshCopTxtHandler");
}
}
#endif
void ThreadHelper::AddDeviceRoleHandler(DeviceRoleHandler aHandler)
{
mDeviceRoleHandlers.emplace_back(aHandler);
}
void ThreadHelper::Scan(ScanHandler aHandler)
{
otError error = OT_ERROR_NONE;
VerifyOrExit(aHandler != nullptr);
mScanHandler = aHandler;
mScanResults.clear();
error =
otLinkActiveScan(mInstance, /*scanChannels =*/0, /*scanDuration=*/0, &ThreadHelper::ActiveScanHandler, this);
exit:
if (error != OT_ERROR_NONE)
{
if (aHandler)
{
mScanHandler(error, {});
}
mScanHandler = nullptr;
}
}
void ThreadHelper::EnergyScan(uint32_t aScanDuration, EnergyScanHandler aHandler)
{
otError error = OT_ERROR_NONE;
uint32_t preferredChannels = otPlatRadioGetPreferredChannelMask(mInstance);
VerifyOrExit(aHandler != nullptr, error = OT_ERROR_BUSY);
VerifyOrExit(aScanDuration < UINT16_MAX, error = OT_ERROR_INVALID_ARGS);
mEnergyScanHandler = aHandler;
mEnergyScanResults.clear();
error = otLinkEnergyScan(mInstance, preferredChannels, static_cast<uint16_t>(aScanDuration),
&ThreadHelper::EnergyScanCallback, this);
exit:
if (error != OT_ERROR_NONE)
{
if (aHandler)
{
mEnergyScanHandler(error, {});
}
mEnergyScanHandler = nullptr;
}
}
void ThreadHelper::RandomFill(void *aBuf, size_t size)
{
std::uniform_int_distribution<> dist(0, UINT8_MAX);
uint8_t *buf = static_cast<uint8_t *>(aBuf);
for (size_t i = 0; i < size; i++)
{
buf[i] = static_cast<uint8_t>(dist(mRandomDevice));
}
}
void ThreadHelper::ActiveScanHandler(otActiveScanResult *aResult, void *aThreadHelper)
{
ThreadHelper *helper = static_cast<ThreadHelper *>(aThreadHelper);
helper->ActiveScanHandler(aResult);
}
void ThreadHelper::ActiveScanHandler(otActiveScanResult *aResult)
{
if (aResult == nullptr)
{
if (mScanHandler != nullptr)
{
mScanHandler(OT_ERROR_NONE, mScanResults);
}
}
else
{
mScanResults.push_back(*aResult);
}
}
#if OTBR_ENABLE_DHCP6_PD && OTBR_ENABLE_BORDER_ROUTING
void ThreadHelper::SetDhcp6PdStateCallback(Dhcp6PdStateCallback aCallback)
{
mDhcp6PdCallback = std::move(aCallback);
otBorderRoutingDhcp6PdSetRequestCallback(mInstance, &ThreadHelper::BorderRoutingDhcp6PdCallback, this);
}
void ThreadHelper::BorderRoutingDhcp6PdCallback(otBorderRoutingDhcp6PdState aState, void *aThreadHelper)
{
ThreadHelper *helper = static_cast<ThreadHelper *>(aThreadHelper);
helper->BorderRoutingDhcp6PdCallback(aState);
}
void ThreadHelper::BorderRoutingDhcp6PdCallback(otBorderRoutingDhcp6PdState aState)
{
if (mDhcp6PdCallback != nullptr)
{
mDhcp6PdCallback(aState);
}
}
#endif // OTBR_ENABLE_DHCP6_PD && OTBR_ENABLE_BORDER_ROUTING
void ThreadHelper::EnergyScanCallback(otEnergyScanResult *aResult, void *aThreadHelper)
{
ThreadHelper *helper = static_cast<ThreadHelper *>(aThreadHelper);
helper->EnergyScanCallback(aResult);
}
void ThreadHelper::EnergyScanCallback(otEnergyScanResult *aResult)
{
if (aResult == nullptr)
{
if (mEnergyScanHandler != nullptr)
{
mEnergyScanHandler(OT_ERROR_NONE, mEnergyScanResults);
}
}
else
{
mEnergyScanResults.push_back(*aResult);
}
}
uint8_t ThreadHelper::RandomChannelFromChannelMask(uint32_t aChannelMask)
{
// 8 bit per byte
constexpr uint8_t kNumChannels = sizeof(aChannelMask) * 8;
uint8_t channels[kNumChannels];
uint8_t numValidChannels = 0;
for (uint8_t i = 0; i < kNumChannels; i++)
{
if (aChannelMask & (1 << i))
{
channels[numValidChannels++] = i;
}
}
return channels[std::uniform_int_distribution<unsigned int>(0, numValidChannels - 1)(mRandomDevice)];
}
static otExtendedPanId ToOtExtendedPanId(uint64_t aExtPanId)
{
otExtendedPanId extPanId;
uint64_t mask = UINT8_MAX;
for (size_t i = 0; i < sizeof(uint64_t); i++)
{
extPanId.m8[i] = static_cast<uint8_t>((aExtPanId >> ((sizeof(uint64_t) - i - 1) * 8)) & mask);
}
return extPanId;
}
void ThreadHelper::Attach(const std::string &aNetworkName,
uint16_t aPanId,
uint64_t aExtPanId,
const std::vector<uint8_t> &aNetworkKey,
const std::vector<uint8_t> &aPSKc,
uint32_t aChannelMask,
AttachHandler aHandler)
{
otError error = OT_ERROR_NONE;
otOperationalDataset dataset = {};
VerifyOrExit(aHandler != nullptr, error = OT_ERROR_INVALID_ARGS);
VerifyOrExit(mAttachHandler == nullptr && mJoinerHandler == nullptr, error = OT_ERROR_INVALID_STATE);
VerifyOrExit(aNetworkKey.empty() || aNetworkKey.size() == sizeof(dataset.mNetworkKey.m8),
error = OT_ERROR_INVALID_ARGS);
VerifyOrExit(aPSKc.empty() || aPSKc.size() == sizeof(dataset.mPskc.m8), error = OT_ERROR_INVALID_ARGS);
VerifyOrExit(aChannelMask != 0, error = OT_ERROR_INVALID_ARGS);
SuccessOrExit(error = otDatasetCreateNewNetwork(mInstance, &dataset));
if (aExtPanId != UINT64_MAX)
{
dataset.mExtendedPanId = ToOtExtendedPanId(aExtPanId);
}
if (!aNetworkKey.empty())
{
memcpy(dataset.mNetworkKey.m8, &aNetworkKey[0], sizeof(dataset.mNetworkKey.m8));
}
if (aPanId != UINT16_MAX)
{
dataset.mPanId = aPanId;
}
if (!aPSKc.empty())
{
memcpy(dataset.mPskc.m8, &aPSKc[0], sizeof(dataset.mPskc.m8));
}
SuccessOrExit(error = otNetworkNameFromString(&dataset.mNetworkName, aNetworkName.c_str()));
dataset.mChannelMask &= aChannelMask;
VerifyOrExit(dataset.mChannelMask != 0, otbrLogWarning("Invalid channel mask"), error = OT_ERROR_INVALID_ARGS);
dataset.mChannel = RandomChannelFromChannelMask(dataset.mChannelMask);
SuccessOrExit(error = otDatasetSetActive(mInstance, &dataset));
if (!otIp6IsEnabled(mInstance))
{
SuccessOrExit(error = otIp6SetEnabled(mInstance, true));
}
SuccessOrExit(error = otThreadSetEnabled(mInstance, true));
mAttachDelayMs = 0;
mAttachHandler = aHandler;
exit:
if (error != OT_ERROR_NONE)
{
if (aHandler)
{
aHandler(error, 0);
}
}
}
void ThreadHelper::Attach(AttachHandler aHandler)
{
otError error = OT_ERROR_NONE;
VerifyOrExit(mAttachHandler == nullptr && mJoinerHandler == nullptr, error = OT_ERROR_INVALID_STATE);
if (!otIp6IsEnabled(mInstance))
{
SuccessOrExit(error = otIp6SetEnabled(mInstance, true));
}
SuccessOrExit(error = otThreadSetEnabled(mInstance, true));
mAttachHandler = aHandler;
exit:
if (error != OT_ERROR_NONE)
{
if (aHandler)
{
aHandler(error, 0);
}
}
}
otError ThreadHelper::Detach(void)
{
otError error = OT_ERROR_NONE;
SuccessOrExit(error = otThreadSetEnabled(mInstance, false));
SuccessOrExit(error = otIp6SetEnabled(mInstance, false));
exit:
return error;
}
otError ThreadHelper::Reset(void)
{
mDeviceRoleHandlers.clear();
otInstanceReset(mInstance);
return OT_ERROR_NONE;
}
void ThreadHelper::JoinerStart(const std::string &aPskd,
const std::string &aProvisioningUrl,
const std::string &aVendorName,
const std::string &aVendorModel,
const std::string &aVendorSwVersion,
const std::string &aVendorData,
ResultHandler aHandler)
{
otError error = OT_ERROR_NONE;
VerifyOrExit(aHandler != nullptr, error = OT_ERROR_INVALID_ARGS);
VerifyOrExit(mAttachHandler == nullptr && mJoinerHandler == nullptr, error = OT_ERROR_INVALID_STATE);
if (!otIp6IsEnabled(mInstance))
{
SuccessOrExit(error = otIp6SetEnabled(mInstance, true));
}
SuccessOrExit(error = otJoinerStart(mInstance, aPskd.c_str(), aProvisioningUrl.c_str(), aVendorName.c_str(),
aVendorModel.c_str(), aVendorSwVersion.c_str(), aVendorData.c_str(),
JoinerCallback, this));
mJoinerHandler = aHandler;
exit:
if (error != OT_ERROR_NONE)
{
if (aHandler)
{
aHandler(error);
}
}
}
void ThreadHelper::JoinerCallback(otError aError, void *aThreadHelper)
{
ThreadHelper *helper = static_cast<ThreadHelper *>(aThreadHelper);
helper->JoinerCallback(aError);
}
void ThreadHelper::JoinerCallback(otError aError)
{
if (aError != OT_ERROR_NONE)
{
otbrLogWarning("Failed to join Thread network: %s", otThreadErrorToString(aError));
mJoinerHandler(aError);
mJoinerHandler = nullptr;
}
else
{
LogOpenThreadResult("Start Thread network", otThreadSetEnabled(mInstance, true));
}
}
otError ThreadHelper::TryResumeNetwork(void)
{
otError error = OT_ERROR_NONE;
if (otLinkGetPanId(mInstance) != UINT16_MAX && mHost->GetDeviceRole() == OT_DEVICE_ROLE_DISABLED)
{
if (!otIp6IsEnabled(mInstance))
{
SuccessOrExit(error = otIp6SetEnabled(mInstance, true));
SuccessOrExit(error = otThreadSetEnabled(mInstance, true));
}
}
exit:
if (error != OT_ERROR_NONE)
{
(void)otIp6SetEnabled(mInstance, false);
}
return error;
}
void ThreadHelper::LogOpenThreadResult(const char *aAction, otError aError)
{
if (aError == OT_ERROR_NONE)
{
otbrLogInfo("%s: %s", aAction, otThreadErrorToString(aError));
}
else
{
otbrLogWarning("%s: %s", aAction, otThreadErrorToString(aError));
}
}
void ThreadHelper::AttachAllNodesTo(const std::vector<uint8_t> &aDatasetTlvs, AttachHandler aHandler)
{
constexpr uint32_t kDelayTimerMilliseconds = 300 * 1000;
otError error = OT_ERROR_NONE;
otOperationalDatasetTlvs datasetTlvs;
otOperationalDataset dataset;
otOperationalDataset emptyDataset{};
otDeviceRole role = mHost->GetDeviceRole();
if (aHandler == nullptr)
{
otbrLogWarning("Attach Handler is nullptr");
ExitNow(error = OT_ERROR_INVALID_ARGS);
}
VerifyOrExit(mAttachHandler == nullptr && mJoinerHandler == nullptr, error = OT_ERROR_BUSY);
VerifyOrExit(aDatasetTlvs.size() <= sizeof(datasetTlvs.mTlvs), error = OT_ERROR_INVALID_ARGS);
std::copy(aDatasetTlvs.begin(), aDatasetTlvs.end(), datasetTlvs.mTlvs);
datasetTlvs.mLength = aDatasetTlvs.size();
SuccessOrExit(error = otDatasetParseTlvs(&datasetTlvs, &dataset));
VerifyOrExit(dataset.mComponents.mIsActiveTimestampPresent, error = OT_ERROR_INVALID_ARGS);
VerifyOrExit(dataset.mComponents.mIsNetworkKeyPresent, error = OT_ERROR_INVALID_ARGS);
VerifyOrExit(dataset.mComponents.mIsNetworkNamePresent, error = OT_ERROR_INVALID_ARGS);
VerifyOrExit(dataset.mComponents.mIsExtendedPanIdPresent, error = OT_ERROR_INVALID_ARGS);
VerifyOrExit(dataset.mComponents.mIsMeshLocalPrefixPresent, error = OT_ERROR_INVALID_ARGS);
VerifyOrExit(dataset.mComponents.mIsPanIdPresent, error = OT_ERROR_INVALID_ARGS);
VerifyOrExit(dataset.mComponents.mIsChannelPresent, error = OT_ERROR_INVALID_ARGS);
VerifyOrExit(dataset.mComponents.mIsPskcPresent, error = OT_ERROR_INVALID_ARGS);
VerifyOrExit(dataset.mComponents.mIsSecurityPolicyPresent, error = OT_ERROR_INVALID_ARGS);
VerifyOrExit(dataset.mComponents.mIsChannelMaskPresent, error = OT_ERROR_INVALID_ARGS);
SuccessOrExit(error = ProcessDatasetForMigration(datasetTlvs, kDelayTimerMilliseconds));
assert(datasetTlvs.mLength > 0);
if (role == OT_DEVICE_ROLE_DISABLED || role == OT_DEVICE_ROLE_DETACHED)
{
otOperationalDataset existingDataset;
bool hasActiveDataset;
error = otDatasetGetActive(mInstance, &existingDataset);
VerifyOrExit(error == OT_ERROR_NONE || error == OT_ERROR_NOT_FOUND);
hasActiveDataset = (error == OT_ERROR_NONE);
if (!hasActiveDataset)
{
SuccessOrExit(error = otDatasetSetActiveTlvs(mInstance, &datasetTlvs));
}
if (!otIp6IsEnabled(mInstance))
{
SuccessOrExit(error = otIp6SetEnabled(mInstance, true));
}
SuccessOrExit(error = otThreadSetEnabled(mInstance, true));
if (hasActiveDataset)
{
mAttachDelayMs = kDelayTimerMilliseconds;
mAttachPendingDatasetTlvs = datasetTlvs;
}
else
{
mAttachDelayMs = 0;
mAttachPendingDatasetTlvs = {};
}
mWaitingMgmtSetResponse = false;
mAttachHandler = aHandler;
ExitNow();
}
SuccessOrExit(error = otDatasetSendMgmtPendingSet(mInstance, &emptyDataset, datasetTlvs.mTlvs, datasetTlvs.mLength,
MgmtSetResponseHandler, this));
mAttachDelayMs = kDelayTimerMilliseconds;
mAttachHandler = aHandler;
mWaitingMgmtSetResponse = true;
exit:
if (error != OT_ERROR_NONE)
{
aHandler(error, 0);
}
}
void ThreadHelper::MgmtSetResponseHandler(otError aResult, void *aContext)
{
static_cast<ThreadHelper *>(aContext)->MgmtSetResponseHandler(aResult);
}
void ThreadHelper::MgmtSetResponseHandler(otError aResult)
{
AttachHandler handler;
int64_t attachDelayMs;
LogOpenThreadResult("MgmtSetResponseHandler()", aResult);
mWaitingMgmtSetResponse = false;
if (mAttachHandler == nullptr)
{
otbrLogWarning("mAttachHandler is nullptr");
mAttachDelayMs = 0;
mAttachPendingDatasetTlvs = {};
ExitNow();
}
switch (aResult)
{
case OT_ERROR_NONE:
case OT_ERROR_REJECTED:
break;
default:
aResult = OT_ERROR_FAILED;
break;
}
attachDelayMs = mAttachDelayMs;
handler = mAttachHandler;
mAttachDelayMs = 0;
mAttachHandler = nullptr;
mAttachPendingDatasetTlvs = {};
if (aResult == OT_ERROR_NONE)
{
handler(aResult, attachDelayMs);
}
else
{
handler(aResult, 0);
}
exit:
return;
}
#if OTBR_ENABLE_UNSECURE_JOIN
otError ThreadHelper::PermitUnsecureJoin(uint16_t aPort, uint32_t aSeconds)
{
otError error = OT_ERROR_NONE;
otExtAddress steeringData;
// 0xff to allow all devices to join
memset(&steeringData.m8, 0xff, sizeof(steeringData.m8));
SuccessOrExit(error = otIp6AddUnsecurePort(mInstance, aPort));
otThreadSetSteeringData(mInstance, &steeringData);
if (aSeconds > 0)
{
auto delay = Milliseconds(aSeconds * 1000);
++mUnsecurePortRefCounter[aPort];
mHost->PostTimerTask(delay, [this, aPort]() {
assert(mUnsecurePortRefCounter.find(aPort) != mUnsecurePortRefCounter.end());
assert(mUnsecurePortRefCounter[aPort] > 0);
if (--mUnsecurePortRefCounter[aPort] == 0)
{
otExtAddress noneAddress;
// 0 to clean steering data
memset(&noneAddress.m8, 0, sizeof(noneAddress.m8));
(void)otIp6RemoveUnsecurePort(mInstance, aPort);
otThreadSetSteeringData(mInstance, &noneAddress);
mUnsecurePortRefCounter.erase(aPort);
}
});
}
else
{
otExtAddress noneAddress;
memset(&noneAddress.m8, 0, sizeof(noneAddress.m8));
(void)otIp6RemoveUnsecurePort(mInstance, aPort);
otThreadSetSteeringData(mInstance, &noneAddress);
}
exit:
return error;
}
#endif
void ThreadHelper::AddActiveDatasetChangeHandler(DatasetChangeHandler aHandler)
{
mActiveDatasetChangeHandlers.push_back(std::move(aHandler));
}
void ThreadHelper::DetachGracefully(ResultHandler aHandler)
{
otError error = OT_ERROR_NONE;
VerifyOrExit(mDetachGracefullyHandler == nullptr, error = OT_ERROR_BUSY);
SuccessOrExit(error = otThreadDetachGracefully(mInstance, &ThreadHelper::DetachGracefullyCallback, this));
mDetachGracefullyHandler = aHandler;
exit:
if (error != OT_ERROR_NONE)
{
aHandler(error);
}
}
void ThreadHelper::DetachGracefullyCallback(void *aContext)
{
static_cast<ThreadHelper *>(aContext)->DetachGracefullyCallback();
}
void ThreadHelper::DetachGracefullyCallback(void)
{
if (mDetachGracefullyHandler != nullptr)
{
mDetachGracefullyHandler(OT_ERROR_NONE);
}
}
#if OTBR_ENABLE_TELEMETRY_DATA_API
#if OTBR_ENABLE_BORDER_ROUTING
void ThreadHelper::RetrieveInfraLinkInfo(threadnetwork::TelemetryData::InfraLinkInfo &aInfraLinkInfo)
{
{
otSysInfraNetIfAddressCounters addressCounters;
uint32_t ifrFlags = otSysGetInfraNetifFlags();
otSysCountInfraNetifAddresses(&addressCounters);
aInfraLinkInfo.set_name(otSysGetInfraNetifName());
aInfraLinkInfo.set_is_up((ifrFlags & IFF_UP) != 0);
aInfraLinkInfo.set_is_running((ifrFlags & IFF_RUNNING) != 0);
aInfraLinkInfo.set_is_multicast((ifrFlags & IFF_MULTICAST) != 0);
aInfraLinkInfo.set_link_local_address_count(addressCounters.mLinkLocalAddresses);
aInfraLinkInfo.set_unique_local_address_count(addressCounters.mUniqueLocalAddresses);
aInfraLinkInfo.set_global_unicast_address_count(addressCounters.mGlobalUnicastAddresses);
}
//---- peer_br_count
{
uint32_t count = 0;
otBorderRoutingPrefixTableIterator iterator;
otBorderRoutingRouterEntry entry;
otBorderRoutingPrefixTableInitIterator(mInstance, &iterator);
while (otBorderRoutingGetNextRouterEntry(mInstance, &iterator, &entry) == OT_ERROR_NONE)
{
if (entry.mIsPeerBr)
{
count++;
}
}
aInfraLinkInfo.set_peer_br_count(count);
}
}
void ThreadHelper::RetrieveExternalRouteInfo(threadnetwork::TelemetryData::ExternalRoutes &aExternalRouteInfo)
{
bool isDefaultRouteAdded = false;
bool isUlaRouteAdded = false;
bool isOthersRouteAdded = false;
Ip6Prefix prefix;
uint16_t rloc16 = otThreadGetRloc16(mInstance);
otNetworkDataIterator iterator = OT_NETWORK_DATA_ITERATOR_INIT;
otExternalRouteConfig config;
while (otNetDataGetNextRoute(mInstance, &iterator, &config) == OT_ERROR_NONE)
{
if (!config.mStable || config.mRloc16 != rloc16)
{
continue;
}
prefix.Set(config.mPrefix);
if (prefix.IsDefaultRoutePrefix())
{
isDefaultRouteAdded = true;
}
else if (prefix.IsUlaPrefix())
{
isUlaRouteAdded = true;
}
else
{
isOthersRouteAdded = true;
}
}
aExternalRouteInfo.set_has_default_route_added(isDefaultRouteAdded);
aExternalRouteInfo.set_has_ula_route_added(isUlaRouteAdded);
aExternalRouteInfo.set_has_others_route_added(isOthersRouteAdded);
}
#endif // OTBR_ENABLE_BORDER_ROUTING
#if OTBR_ENABLE_DHCP6_PD
void ThreadHelper::RetrievePdInfo(threadnetwork::TelemetryData::WpanBorderRouter *aWpanBorderRouter)
{
aWpanBorderRouter->set_dhcp6_pd_state(Dhcp6PdStateFromOtDhcp6PdState(otBorderRoutingDhcp6PdGetState(mInstance)));
RetrieveHashedPdPrefix(aWpanBorderRouter->mutable_hashed_pd_prefix());
RetrievePdProcessedRaInfo(aWpanBorderRouter->mutable_pd_processed_ra_info());
}
void ThreadHelper::RetrieveHashedPdPrefix(std::string *aHashedPdPrefix)
{
otBorderRoutingPrefixTableEntry aPrefixInfo;
const uint8_t *prefixAddr = nullptr;
const uint8_t *truncatedHash = nullptr;
constexpr size_t kHashPrefixLength = 6;
constexpr size_t kHashedPrefixLength = 2;
std::vector<uint8_t> hashedPdHeader = {0x20, 0x01, 0x0d, 0xb8};
std::vector<uint8_t> hashedPdTailer = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
std::vector<uint8_t> hashedPdPrefix;
hashedPdPrefix.reserve(16);
Sha256 sha256;
Sha256::Hash hash;
SuccessOrExit(otBorderRoutingGetPdOmrPrefix(mInstance, &aPrefixInfo));
prefixAddr = aPrefixInfo.mPrefix.mPrefix.mFields.m8;
// TODO: Put below steps into a reusable function.
sha256.Start();
sha256.Update(prefixAddr, kHashPrefixLength);
sha256.Update(mNat64PdCommonSalt, kNat64PdCommonHashSaltLength);
sha256.Finish(hash);
// Append hashedPdHeader
hashedPdPrefix.insert(hashedPdPrefix.end(), hashedPdHeader.begin(), hashedPdHeader.end());
// Append the first 2 bytes of the hashed prefix
truncatedHash = hash.GetBytes();
hashedPdPrefix.insert(hashedPdPrefix.end(), truncatedHash, truncatedHash + kHashedPrefixLength);
// Append ip[6] and ip[7]
hashedPdPrefix.push_back(prefixAddr[6]);
hashedPdPrefix.push_back(prefixAddr[7]);
// Append hashedPdTailer
hashedPdPrefix.insert(hashedPdPrefix.end(), hashedPdTailer.begin(), hashedPdTailer.end());
aHashedPdPrefix->append(reinterpret_cast<const char *>(hashedPdPrefix.data()), hashedPdPrefix.size());
exit:
return;
}
void ThreadHelper::RetrievePdProcessedRaInfo(threadnetwork::TelemetryData::PdProcessedRaInfo *aPdProcessedRaInfo)
{
otPdProcessedRaInfo raInfo;
SuccessOrExit(otBorderRoutingGetPdProcessedRaInfo(mInstance, &raInfo));
aPdProcessedRaInfo->set_num_platform_ra_received(raInfo.mNumPlatformRaReceived);
aPdProcessedRaInfo->set_num_platform_pio_processed(raInfo.mNumPlatformPioProcessed);
aPdProcessedRaInfo->set_last_platform_ra_msec(raInfo.mLastPlatformRaMsec);
exit:
return;
}
#endif // OTBR_ENABLE_DHCP6_PD
#if OTBR_ENABLE_BORDER_AGENT
void ThreadHelper::RetrieveBorderAgentInfo(threadnetwork::TelemetryData::BorderAgentInfo *aBorderAgentInfo)
{
auto baCounters = aBorderAgentInfo->mutable_border_agent_counters();
auto otBorderAgentCounters = *otBorderAgentGetCounters(mInstance);
baCounters->set_epskc_activations(otBorderAgentCounters.mEpskcActivations);
baCounters->set_epskc_deactivation_clears(otBorderAgentCounters.mEpskcDeactivationClears);
baCounters->set_epskc_deactivation_timeouts(otBorderAgentCounters.mEpskcDeactivationTimeouts);
baCounters->set_epskc_deactivation_max_attempts(otBorderAgentCounters.mEpskcDeactivationMaxAttempts);
baCounters->set_epskc_deactivation_disconnects(otBorderAgentCounters.mEpskcDeactivationDisconnects);
baCounters->set_epskc_invalid_ba_state_errors(otBorderAgentCounters.mEpskcInvalidBaStateErrors);
baCounters->set_epskc_invalid_args_errors(otBorderAgentCounters.mEpskcInvalidArgsErrors);
baCounters->set_epskc_start_secure_session_errors(otBorderAgentCounters.mEpskcStartSecureSessionErrors);
baCounters->set_epskc_secure_session_successes(otBorderAgentCounters.mEpskcSecureSessionSuccesses);
baCounters->set_epskc_secure_session_failures(otBorderAgentCounters.mEpskcSecureSessionFailures);
baCounters->set_epskc_commissioner_petitions(otBorderAgentCounters.mEpskcCommissionerPetitions);
baCounters->set_pskc_secure_session_successes(otBorderAgentCounters.mPskcSecureSessionSuccesses);
baCounters->set_pskc_secure_session_failures(otBorderAgentCounters.mPskcSecureSessionFailures);
baCounters->set_pskc_commissioner_petitions(otBorderAgentCounters.mPskcCommissionerPetitions);
baCounters->set_mgmt_active_get_reqs(otBorderAgentCounters.mMgmtActiveGets);
baCounters->set_mgmt_pending_get_reqs(otBorderAgentCounters.mMgmtPendingGets);
}
#endif
otError ThreadHelper::RetrieveTelemetryData(Mdns::Publisher *aPublisher, threadnetwork::TelemetryData &telemetryData)
{
otError error = OT_ERROR_NONE;
std::vector<otNeighborInfo> neighborTable;
// Begin of WpanStats section.
auto wpanStats = telemetryData.mutable_wpan_stats();
{
otDeviceRole role = mHost->GetDeviceRole();
otLinkModeConfig otCfg = otThreadGetLinkMode(mInstance);
wpanStats->set_node_type(TelemetryNodeTypeFromRoleAndLinkMode(role, otCfg));
}
wpanStats->set_channel(otLinkGetChannel(mInstance));
{
uint16_t ccaFailureRate = otLinkGetCcaFailureRate(mInstance);
wpanStats->set_mac_cca_fail_rate(static_cast<float>(ccaFailureRate) / 0xffff);
}
{
int8_t radioTxPower;
if (otPlatRadioGetTransmitPower(mInstance, &radioTxPower) == OT_ERROR_NONE)
{
wpanStats->set_radio_tx_power(radioTxPower);
}
else
{
error = OT_ERROR_FAILED;
}
}
{
const otMacCounters *linkCounters = otLinkGetCounters(mInstance);
wpanStats->set_phy_rx(linkCounters->mRxTotal);
wpanStats->set_phy_tx(linkCounters->mTxTotal);
wpanStats->set_mac_unicast_rx(linkCounters->mRxUnicast);
wpanStats->set_mac_unicast_tx(linkCounters->mTxUnicast);
wpanStats->set_mac_broadcast_rx(linkCounters->mRxBroadcast);
wpanStats->set_mac_broadcast_tx(linkCounters->mTxBroadcast);
wpanStats->set_mac_tx_ack_req(linkCounters->mTxAckRequested);
wpanStats->set_mac_tx_no_ack_req(linkCounters->mTxNoAckRequested);
wpanStats->set_mac_tx_acked(linkCounters->mTxAcked);
wpanStats->set_mac_tx_data(linkCounters->mTxData);
wpanStats->set_mac_tx_data_poll(linkCounters->mTxDataPoll);
wpanStats->set_mac_tx_beacon(linkCounters->mTxBeacon);
wpanStats->set_mac_tx_beacon_req(linkCounters->mTxBeaconRequest);
wpanStats->set_mac_tx_other_pkt(linkCounters->mTxOther);
wpanStats->set_mac_tx_retry(linkCounters->mTxRetry);
wpanStats->set_mac_rx_data(linkCounters->mRxData);
wpanStats->set_mac_rx_data_poll(linkCounters->mRxDataPoll);
wpanStats->set_mac_rx_beacon(linkCounters->mRxBeacon);
wpanStats->set_mac_rx_beacon_req(linkCounters->mRxBeaconRequest);
wpanStats->set_mac_rx_other_pkt(linkCounters->mRxOther);
wpanStats->set_mac_rx_filter_whitelist(linkCounters->mRxAddressFiltered);
wpanStats->set_mac_rx_filter_dest_addr(linkCounters->mRxDestAddrFiltered);
wpanStats->set_mac_tx_fail_cca(linkCounters->mTxErrCca);
wpanStats->set_mac_rx_fail_decrypt(linkCounters->mRxErrSec);
wpanStats->set_mac_rx_fail_no_frame(linkCounters->mRxErrNoFrame);
wpanStats->set_mac_rx_fail_unknown_neighbor(linkCounters->mRxErrUnknownNeighbor);
wpanStats->set_mac_rx_fail_invalid_src_addr(linkCounters->mRxErrInvalidSrcAddr);
wpanStats->set_mac_rx_fail_fcs(linkCounters->mRxErrFcs);
wpanStats->set_mac_rx_fail_other(linkCounters->mRxErrOther);
}
{
const otIpCounters *ipCounters = otThreadGetIp6Counters(mInstance);
wpanStats->set_ip_tx_success(ipCounters->mTxSuccess);
wpanStats->set_ip_rx_success(ipCounters->mRxSuccess);
wpanStats->set_ip_tx_failure(ipCounters->mTxFailure);
wpanStats->set_ip_rx_failure(ipCounters->mRxFailure);
}
// End of WpanStats section.
{
// Begin of WpanTopoFull section.
auto wpanTopoFull = telemetryData.mutable_wpan_topo_full();
uint16_t rloc16 = otThreadGetRloc16(mInstance);
wpanTopoFull->set_rloc16(rloc16);
{
otRouterInfo info;
if (otThreadGetRouterInfo(mInstance, rloc16, &info) == OT_ERROR_NONE)
{
wpanTopoFull->set_router_id(info.mRouterId);
}
else
{
error = OT_ERROR_FAILED;
}
}
otNeighborInfoIterator iter = OT_NEIGHBOR_INFO_ITERATOR_INIT;
otNeighborInfo neighborInfo;
while (otThreadGetNextNeighborInfo(mInstance, &iter, &neighborInfo) == OT_ERROR_NONE)
{
neighborTable.push_back(neighborInfo);
}
wpanTopoFull->set_neighbor_table_size(neighborTable.size());
uint16_t childIndex = 0;
otChildInfo childInfo;
std::vector<otChildInfo> childTable;
while (otThreadGetChildInfoByIndex(mInstance, childIndex, &childInfo) == OT_ERROR_NONE)
{
childTable.push_back(childInfo);
childIndex++;
}
wpanTopoFull->set_child_table_size(childTable.size());
{
struct otLeaderData leaderData;
if (otThreadGetLeaderData(mInstance, &leaderData) == OT_ERROR_NONE)
{
wpanTopoFull->set_leader_router_id(leaderData.mLeaderRouterId);
wpanTopoFull->set_leader_weight(leaderData.mWeighting);
wpanTopoFull->set_network_data_version(leaderData.mDataVersion);
wpanTopoFull->set_stable_network_data_version(leaderData.mStableDataVersion);
}
else
{
error = OT_ERROR_FAILED;
}
}
uint8_t weight = otThreadGetLocalLeaderWeight(mInstance);
wpanTopoFull->set_leader_local_weight(weight);
uint32_t partitionId = otThreadGetPartitionId(mInstance);
wpanTopoFull->set_partition_id(partitionId);
static constexpr size_t kNetworkDataMaxSize = 255;
{
uint8_t data[kNetworkDataMaxSize];
uint8_t len = sizeof(data);
std::vector<uint8_t> networkData;
if (otNetDataGet(mInstance, /*stable=*/false, data, &len) == OT_ERROR_NONE)
{
networkData = std::vector<uint8_t>(&data[0], &data[len]);
wpanTopoFull->set_network_data(std::string(networkData.begin(), networkData.end()));
}
else
{
error = OT_ERROR_FAILED;
}
}
{
uint8_t data[kNetworkDataMaxSize];
uint8_t len = sizeof(data);
std::vector<uint8_t> networkData;
if (otNetDataGet(mInstance, /*stable=*/true, data, &len) == OT_ERROR_NONE)
{
networkData = std::vector<uint8_t>(&data[0], &data[len]);
wpanTopoFull->set_stable_network_data(std::string(networkData.begin(), networkData.end()));
}
else
{
error = OT_ERROR_FAILED;
}
}
int8_t rssi = otPlatRadioGetRssi(mInstance);
wpanTopoFull->set_instant_rssi(rssi);
const otExtendedPanId *extPanId = otThreadGetExtendedPanId(mInstance);
uint64_t extPanIdVal;
extPanIdVal = ConvertOpenThreadUint64(extPanId->m8);
wpanTopoFull->set_extended_pan_id(extPanIdVal);
#if OTBR_ENABLE_BORDER_ROUTING
wpanTopoFull->set_peer_br_count(otBorderRoutingCountPeerBrs(mInstance, /*minAge=*/nullptr));
#endif
// End of WpanTopoFull section.
// Begin of TopoEntry section.
std::map<uint16_t, const otChildInfo *> childMap;
for (const otChildInfo &childInfo : childTable)
{
auto pair = childMap.insert({childInfo.mRloc16, &childInfo});
if (!pair.second)
{
// This shouldn't happen, so log an error. It doesn't matter which
// duplicate is kept.
otbrLogErr("Children with duplicate RLOC16 found: 0x%04x", static_cast<int>(childInfo.mRloc16));
}
}
for (const otNeighborInfo &neighborInfo : neighborTable)
{
auto topoEntry = telemetryData.add_topo_entries();
topoEntry->set_rloc16(neighborInfo.mRloc16);
topoEntry->mutable_age()->set_seconds(neighborInfo.mAge);
topoEntry->set_link_quality_in(neighborInfo.mLinkQualityIn);
topoEntry->set_average_rssi(neighborInfo.mAverageRssi);
topoEntry->set_last_rssi(neighborInfo.mLastRssi);
topoEntry->set_link_frame_counter(neighborInfo.mLinkFrameCounter);
topoEntry->set_mle_frame_counter(neighborInfo.mMleFrameCounter);
topoEntry->set_rx_on_when_idle(neighborInfo.mRxOnWhenIdle);
topoEntry->set_secure_data_request(true);
topoEntry->set_full_function(neighborInfo.mFullThreadDevice);
topoEntry->set_full_network_data(neighborInfo.mFullNetworkData);
topoEntry->set_mac_frame_error_rate(static_cast<float>(neighborInfo.mFrameErrorRate) / 0xffff);
topoEntry->set_ip_message_error_rate(static_cast<float>(neighborInfo.mMessageErrorRate) / 0xffff);
topoEntry->set_version(neighborInfo.mVersion);
if (!neighborInfo.mIsChild)
{
continue;
}
auto it = childMap.find(neighborInfo.mRloc16);
if (it == childMap.end())
{
otbrLogErr("Neighbor 0x%04x not found in child table", static_cast<int>(neighborInfo.mRloc16));
continue;
}
const otChildInfo *childInfo = it->second;
topoEntry->set_is_child(true);
topoEntry->mutable_timeout()->set_seconds(childInfo->mTimeout);
topoEntry->set_network_data_version(childInfo->mNetworkDataVersion);
}
// End of TopoEntry section.
}
{
// Begin of WpanBorderRouter section.
auto wpanBorderRouter = telemetryData.mutable_wpan_border_router();
// Begin of BorderRoutingCounters section.
auto borderRoutingCouters = wpanBorderRouter->mutable_border_routing_counters();
const otBorderRoutingCounters *otBorderRoutingCounters = otIp6GetBorderRoutingCounters(mInstance);
borderRoutingCouters->mutable_inbound_unicast()->set_packet_count(
otBorderRoutingCounters->mInboundUnicast.mPackets);
borderRoutingCouters->mutable_inbound_unicast()->set_byte_count(
otBorderRoutingCounters->mInboundUnicast.mBytes);
borderRoutingCouters->mutable_inbound_multicast()->set_packet_count(
otBorderRoutingCounters->mInboundMulticast.mPackets);
borderRoutingCouters->mutable_inbound_multicast()->set_byte_count(
otBorderRoutingCounters->mInboundMulticast.mBytes);
borderRoutingCouters->mutable_outbound_unicast()->set_packet_count(
otBorderRoutingCounters->mOutboundUnicast.mPackets);
borderRoutingCouters->mutable_outbound_unicast()->set_byte_count(
otBorderRoutingCounters->mOutboundUnicast.mBytes);
borderRoutingCouters->mutable_outbound_multicast()->set_packet_count(
otBorderRoutingCounters->mOutboundMulticast.mPackets);
borderRoutingCouters->mutable_outbound_multicast()->set_byte_count(
otBorderRoutingCounters->mOutboundMulticast.mBytes);
borderRoutingCouters->set_ra_rx(otBorderRoutingCounters->mRaRx);
borderRoutingCouters->set_ra_tx_success(otBorderRoutingCounters->mRaTxSuccess);
borderRoutingCouters->set_ra_tx_failure(otBorderRoutingCounters->mRaTxFailure);
borderRoutingCouters->set_rs_rx(otBorderRoutingCounters->mRsRx);
borderRoutingCouters->set_rs_tx_success(otBorderRoutingCounters->mRsTxSuccess);
borderRoutingCouters->set_rs_tx_failure(otBorderRoutingCounters->mRsTxFailure);
borderRoutingCouters->mutable_inbound_internet()->set_packet_count(
otBorderRoutingCounters->mInboundInternet.mPackets);
borderRoutingCouters->mutable_inbound_internet()->set_byte_count(
otBorderRoutingCounters->mInboundInternet.mBytes);
borderRoutingCouters->mutable_outbound_internet()->set_packet_count(
otBorderRoutingCounters->mOutboundInternet.mPackets);
borderRoutingCouters->mutable_outbound_internet()->set_byte_count(
otBorderRoutingCounters->mOutboundInternet.mBytes);
#if OTBR_ENABLE_NAT64
{
auto nat64IcmpCounters = borderRoutingCouters->mutable_nat64_protocol_counters()->mutable_icmp();
auto nat64UdpCounters = borderRoutingCouters->mutable_nat64_protocol_counters()->mutable_udp();
auto nat64TcpCounters = borderRoutingCouters->mutable_nat64_protocol_counters()->mutable_tcp();
otNat64ProtocolCounters otCounters;
otNat64GetCounters(mInstance, &otCounters);
nat64IcmpCounters->set_ipv4_to_ipv6_packets(otCounters.mIcmp.m4To6Packets);
nat64IcmpCounters->set_ipv4_to_ipv6_bytes(otCounters.mIcmp.m4To6Bytes);
nat64IcmpCounters->set_ipv6_to_ipv4_packets(otCounters.mIcmp.m6To4Packets);
nat64IcmpCounters->set_ipv6_to_ipv4_bytes(otCounters.mIcmp.m6To4Bytes);
nat64UdpCounters->set_ipv4_to_ipv6_packets(otCounters.mUdp.m4To6Packets);
nat64UdpCounters->set_ipv4_to_ipv6_bytes(otCounters.mUdp.m4To6Bytes);
nat64UdpCounters->set_ipv6_to_ipv4_packets(otCounters.mUdp.m6To4Packets);
nat64UdpCounters->set_ipv6_to_ipv4_bytes(otCounters.mUdp.m6To4Bytes);
nat64TcpCounters->set_ipv4_to_ipv6_packets(otCounters.mTcp.m4To6Packets);
nat64TcpCounters->set_ipv4_to_ipv6_bytes(otCounters.mTcp.m4To6Bytes);
nat64TcpCounters->set_ipv6_to_ipv4_packets(otCounters.mTcp.m6To4Packets);
nat64TcpCounters->set_ipv6_to_ipv4_bytes(otCounters.mTcp.m6To4Bytes);
}
{
auto errorCounters = borderRoutingCouters->mutable_nat64_error_counters();
otNat64ErrorCounters otCounters;
otNat64GetErrorCounters(mInstance, &otCounters);
errorCounters->mutable_unknown()->set_ipv4_to_ipv6_packets(
otCounters.mCount4To6[OT_NAT64_DROP_REASON_UNKNOWN]);
errorCounters->mutable_unknown()->set_ipv6_to_ipv4_packets(
otCounters.mCount6To4[OT_NAT64_DROP_REASON_UNKNOWN]);
errorCounters->mutable_illegal_packet()->set_ipv4_to_ipv6_packets(
otCounters.mCount4To6[OT_NAT64_DROP_REASON_ILLEGAL_PACKET]);
errorCounters->mutable_illegal_packet()->set_ipv6_to_ipv4_packets(
otCounters.mCount6To4[OT_NAT64_DROP_REASON_ILLEGAL_PACKET]);
errorCounters->mutable_unsupported_protocol()->set_ipv4_to_ipv6_packets(
otCounters.mCount4To6[OT_NAT64_DROP_REASON_UNSUPPORTED_PROTO]);
errorCounters->mutable_unsupported_protocol()->set_ipv6_to_ipv4_packets(
otCounters.mCount6To4[OT_NAT64_DROP_REASON_UNSUPPORTED_PROTO]);
errorCounters->mutable_no_mapping()->set_ipv4_to_ipv6_packets(
otCounters.mCount4To6[OT_NAT64_DROP_REASON_NO_MAPPING]);
errorCounters->mutable_no_mapping()->set_ipv6_to_ipv4_packets(
otCounters.mCount6To4[OT_NAT64_DROP_REASON_NO_MAPPING]);
}
#endif // OTBR_ENABLE_NAT64
// End of BorderRoutingCounters section.
#if OTBR_ENABLE_TREL
// Begin of TrelInfo section.
{
auto trelInfo = wpanBorderRouter->mutable_trel_info();
auto otTrelCounters = otTrelGetCounters(mInstance);
auto trelCounters = trelInfo->mutable_counters();
trelInfo->set_is_trel_enabled(otTrelIsEnabled(mInstance));
trelInfo->set_num_trel_peers(otTrelGetNumberOfPeers(mInstance));
trelCounters->set_trel_tx_packets(otTrelCounters->mTxPackets);
trelCounters->set_trel_tx_bytes(otTrelCounters->mTxBytes);
trelCounters->set_trel_tx_packets_failed(otTrelCounters->mTxFailure);
trelCounters->set_tre_rx_packets(otTrelCounters->mRxPackets);
trelCounters->set_trel_rx_bytes(otTrelCounters->mRxBytes);
}
// End of TrelInfo section.
#endif // OTBR_ENABLE_TREL
#if OTBR_ENABLE_BORDER_ROUTING
RetrieveInfraLinkInfo(*wpanBorderRouter->mutable_infra_link_info());
RetrieveExternalRouteInfo(*wpanBorderRouter->mutable_external_route_info());
#endif
#if OTBR_ENABLE_SRP_ADVERTISING_PROXY
// Begin of SrpServerInfo section.
{
auto srpServer = wpanBorderRouter->mutable_srp_server();
otSrpServerLeaseInfo leaseInfo;
const otSrpServerHost *host = nullptr;
const otSrpServerResponseCounters *responseCounters = otSrpServerGetResponseCounters(mInstance);
srpServer->set_state(SrpServerStateFromOtSrpServerState(otSrpServerGetState(mInstance)));
srpServer->set_port(otSrpServerGetPort(mInstance));
srpServer->set_address_mode(
SrpServerAddressModeFromOtSrpServerAddressMode(otSrpServerGetAddressMode(mInstance)));
auto srpServerHosts = srpServer->mutable_hosts();
auto srpServerServices = srpServer->mutable_services();
auto srpServerResponseCounters = srpServer->mutable_response_counters();
while ((host = otSrpServerGetNextHost(mInstance, host)))
{
const otSrpServerService *service = nullptr;
if (otSrpServerHostIsDeleted(host))
{
srpServerHosts->set_deleted_count(srpServerHosts->deleted_count() + 1);
}
else
{
srpServerHosts->set_fresh_count(srpServerHosts->fresh_count() + 1);
otSrpServerHostGetLeaseInfo(host, &leaseInfo);
srpServerHosts->set_lease_time_total_ms(srpServerHosts->lease_time_total_ms() + leaseInfo.mLease);
srpServerHosts->set_key_lease_time_total_ms(srpServerHosts->key_lease_time_total_ms() +
leaseInfo.mKeyLease);
srpServerHosts->set_remaining_lease_time_total_ms(srpServerHosts->remaining_lease_time_total_ms() +
leaseInfo.mRemainingLease);
srpServerHosts->set_remaining_key_lease_time_total_ms(
srpServerHosts->remaining_key_lease_time_total_ms() + leaseInfo.mRemainingKeyLease);
}
while ((service = otSrpServerHostGetNextService(host, service)))
{
if (otSrpServerServiceIsDeleted(service))
{
srpServerServices->set_deleted_count(srpServerServices->deleted_count() + 1);
}
else
{
srpServerServices->set_fresh_count(srpServerServices->fresh_count() + 1);
otSrpServerServiceGetLeaseInfo(service, &leaseInfo);
srpServerServices->set_lease_time_total_ms(srpServerServices->lease_time_total_ms() +
leaseInfo.mLease);
srpServerServices->set_key_lease_time_total_ms(srpServerServices->key_lease_time_total_ms() +
leaseInfo.mKeyLease);
srpServerServices->set_remaining_lease_time_total_ms(
srpServerServices->remaining_lease_time_total_ms() + leaseInfo.mRemainingLease);
srpServerServices->set_remaining_key_lease_time_total_ms(
srpServerServices->remaining_key_lease_time_total_ms() + leaseInfo.mRemainingKeyLease);
}
}
}
srpServerResponseCounters->set_success_count(responseCounters->mSuccess);
srpServerResponseCounters->set_server_failure_count(responseCounters->mServerFailure);
srpServerResponseCounters->set_format_error_count(responseCounters->mFormatError);
srpServerResponseCounters->set_name_exists_count(responseCounters->mNameExists);
srpServerResponseCounters->set_refused_count(responseCounters->mRefused);
srpServerResponseCounters->set_other_count(responseCounters->mOther);
}
// End of SrpServerInfo section.
#endif // OTBR_ENABLE_SRP_ADVERTISING_PROXY
#if OTBR_ENABLE_DNSSD_DISCOVERY_PROXY
// Begin of DnsServerInfo section.
{
auto dnsServer = wpanBorderRouter->mutable_dns_server();
auto dnsServerResponseCounters = dnsServer->mutable_response_counters();
otDnssdCounters otDnssdCounters = *otDnssdGetCounters(mInstance);
dnsServerResponseCounters->set_success_count(otDnssdCounters.mSuccessResponse);
dnsServerResponseCounters->set_server_failure_count(otDnssdCounters.mServerFailureResponse);
dnsServerResponseCounters->set_format_error_count(otDnssdCounters.mFormatErrorResponse);
dnsServerResponseCounters->set_name_error_count(otDnssdCounters.mNameErrorResponse);
dnsServerResponseCounters->set_not_implemented_count(otDnssdCounters.mNotImplementedResponse);
dnsServerResponseCounters->set_other_count(otDnssdCounters.mOtherResponse);
// The counters of queries, responses, failures handled by upstream DNS server.
dnsServerResponseCounters->set_upstream_dns_queries(otDnssdCounters.mUpstreamDnsCounters.mQueries);
dnsServerResponseCounters->set_upstream_dns_responses(otDnssdCounters.mUpstreamDnsCounters.mResponses);
dnsServerResponseCounters->set_upstream_dns_failures(otDnssdCounters.mUpstreamDnsCounters.mFailures);
dnsServer->set_resolved_by_local_srp_count(otDnssdCounters.mResolvedBySrp);
#if OTBR_ENABLE_DNS_UPSTREAM_QUERY
dnsServer->set_upstream_dns_query_state(
otDnssdUpstreamQueryIsEnabled(mInstance)
? threadnetwork::TelemetryData::UPSTREAMDNS_QUERY_STATE_ENABLED
: threadnetwork::TelemetryData::UPSTREAMDNS_QUERY_STATE_DISABLED);
#endif // OTBR_ENABLE_DNS_UPSTREAM_QUERY
}
// End of DnsServerInfo section.
#endif // OTBR_ENABLE_DNSSD_DISCOVERY_PROXY
// Start of MdnsInfo section.
if (aPublisher != nullptr)
{
auto mdns = wpanBorderRouter->mutable_mdns();
const MdnsTelemetryInfo &mdnsInfo = aPublisher->GetMdnsTelemetryInfo();
CopyMdnsResponseCounters(mdnsInfo.mHostRegistrations, mdns->mutable_host_registration_responses());
CopyMdnsResponseCounters(mdnsInfo.mServiceRegistrations, mdns->mutable_service_registration_responses());
CopyMdnsResponseCounters(mdnsInfo.mHostResolutions, mdns->mutable_host_resolution_responses());
CopyMdnsResponseCounters(mdnsInfo.mServiceResolutions, mdns->mutable_service_resolution_responses());
mdns->set_host_registration_ema_latency_ms(mdnsInfo.mHostRegistrationEmaLatency);
mdns->set_service_registration_ema_latency_ms(mdnsInfo.mServiceRegistrationEmaLatency);
mdns->set_host_resolution_ema_latency_ms(mdnsInfo.mHostResolutionEmaLatency);
mdns->set_service_resolution_ema_latency_ms(mdnsInfo.mServiceResolutionEmaLatency);
}
// End of MdnsInfo section.
#if OTBR_ENABLE_NAT64
// Start of BorderRoutingNat64State section.
{
auto nat64State = wpanBorderRouter->mutable_nat64_state();
nat64State->set_prefix_manager_state(Nat64StateFromOtNat64State(otNat64GetPrefixManagerState(mInstance)));
nat64State->set_translator_state(Nat64StateFromOtNat64State(otNat64GetTranslatorState(mInstance)));
}
// End of BorderRoutingNat64State section.
// Start of Nat64Mapping section.
{
otNat64AddressMappingIterator iterator;
otNat64AddressMapping otMapping;
Sha256::Hash hash;
Sha256 sha256;
otNat64InitAddressMappingIterator(mInstance, &iterator);
while (otNat64GetNextAddressMapping(mInstance, &iterator, &otMapping) == OT_ERROR_NONE)
{
auto nat64Mapping = wpanBorderRouter->add_nat64_mappings();
auto nat64MappingCounters = nat64Mapping->mutable_counters();
nat64Mapping->set_mapping_id(otMapping.mId);
CopyNat64TrafficCounters(otMapping.mCounters.mTcp, nat64MappingCounters->mutable_tcp());
CopyNat64TrafficCounters(otMapping.mCounters.mUdp, nat64MappingCounters->mutable_udp());
CopyNat64TrafficCounters(otMapping.mCounters.mIcmp, nat64MappingCounters->mutable_icmp());
sha256.Start();
sha256.Update(otMapping.mIp6.mFields.m8, sizeof(otMapping.mIp6.mFields.m8));
sha256.Update(mNat64PdCommonSalt, sizeof(mNat64PdCommonSalt));
sha256.Finish(hash);
nat64Mapping->mutable_hashed_ipv6_address()->append(reinterpret_cast<const char *>(hash.GetBytes()),
Sha256::Hash::kSize);
// Remaining time is not included in the telemetry
}
}
// End of Nat64Mapping section.
#endif // OTBR_ENABLE_NAT64
#if OTBR_ENABLE_DHCP6_PD
RetrievePdInfo(wpanBorderRouter);
#endif // OTBR_ENABLE_DHCP6_PD
#if OTBR_ENABLE_BORDER_AGENT
RetrieveBorderAgentInfo(wpanBorderRouter->mutable_border_agent_info());
#endif // OTBR_ENABLE_BORDER_AGENT
// End of WpanBorderRouter section.
// Start of WpanRcp section.
{
auto wpanRcp = telemetryData.mutable_wpan_rcp();
const otRadioSpinelMetrics *otRadioSpinelMetrics = otSysGetRadioSpinelMetrics();
auto rcpStabilityStatistics = wpanRcp->mutable_rcp_stability_statistics();
if (otRadioSpinelMetrics != nullptr)
{
rcpStabilityStatistics->set_rcp_timeout_count(otRadioSpinelMetrics->mRcpTimeoutCount);
rcpStabilityStatistics->set_rcp_reset_count(otRadioSpinelMetrics->mRcpUnexpectedResetCount);
rcpStabilityStatistics->set_rcp_restoration_count(otRadioSpinelMetrics->mRcpRestorationCount);
rcpStabilityStatistics->set_spinel_parse_error_count(otRadioSpinelMetrics->mSpinelParseErrorCount);
}
// TODO: provide rcp_firmware_update_count info.
rcpStabilityStatistics->set_thread_stack_uptime(otInstanceGetUptime(mInstance));
const otRcpInterfaceMetrics *otRcpInterfaceMetrics = otSysGetRcpInterfaceMetrics();
if (otRcpInterfaceMetrics != nullptr)
{
auto rcpInterfaceStatistics = wpanRcp->mutable_rcp_interface_statistics();
rcpInterfaceStatistics->set_rcp_interface_type(otRcpInterfaceMetrics->mRcpInterfaceType);
rcpInterfaceStatistics->set_transferred_frames_count(otRcpInterfaceMetrics->mTransferredFrameCount);
rcpInterfaceStatistics->set_transferred_valid_frames_count(
otRcpInterfaceMetrics->mTransferredValidFrameCount);
rcpInterfaceStatistics->set_transferred_garbage_frames_count(
otRcpInterfaceMetrics->mTransferredGarbageFrameCount);
rcpInterfaceStatistics->set_rx_frames_count(otRcpInterfaceMetrics->mRxFrameCount);
rcpInterfaceStatistics->set_rx_bytes_count(otRcpInterfaceMetrics->mRxFrameByteCount);
rcpInterfaceStatistics->set_tx_frames_count(otRcpInterfaceMetrics->mTxFrameCount);
rcpInterfaceStatistics->set_tx_bytes_count(otRcpInterfaceMetrics->mTxFrameByteCount);
}
}
// End of WpanRcp section.
// Start of CoexMetrics section.
{
auto coexMetrics = telemetryData.mutable_coex_metrics();
otRadioCoexMetrics otRadioCoexMetrics;
if (otPlatRadioGetCoexMetrics(mInstance, &otRadioCoexMetrics) == OT_ERROR_NONE)
{
coexMetrics->set_count_tx_request(otRadioCoexMetrics.mNumTxRequest);
coexMetrics->set_count_tx_grant_immediate(otRadioCoexMetrics.mNumTxGrantImmediate);
coexMetrics->set_count_tx_grant_wait(otRadioCoexMetrics.mNumTxGrantWait);
coexMetrics->set_count_tx_grant_wait_activated(otRadioCoexMetrics.mNumTxGrantWaitActivated);
coexMetrics->set_count_tx_grant_wait_timeout(otRadioCoexMetrics.mNumTxGrantWaitTimeout);
coexMetrics->set_count_tx_grant_deactivated_during_request(
otRadioCoexMetrics.mNumTxGrantDeactivatedDuringRequest);
coexMetrics->set_tx_average_request_to_grant_time_us(otRadioCoexMetrics.mAvgTxRequestToGrantTime);
coexMetrics->set_count_rx_request(otRadioCoexMetrics.mNumRxRequest);
coexMetrics->set_count_rx_grant_immediate(otRadioCoexMetrics.mNumRxGrantImmediate);
coexMetrics->set_count_rx_grant_wait(otRadioCoexMetrics.mNumRxGrantWait);
coexMetrics->set_count_rx_grant_wait_activated(otRadioCoexMetrics.mNumRxGrantWaitActivated);
coexMetrics->set_count_rx_grant_wait_timeout(otRadioCoexMetrics.mNumRxGrantWaitTimeout);
coexMetrics->set_count_rx_grant_deactivated_during_request(
otRadioCoexMetrics.mNumRxGrantDeactivatedDuringRequest);
coexMetrics->set_count_rx_grant_none(otRadioCoexMetrics.mNumRxGrantNone);
coexMetrics->set_rx_average_request_to_grant_time_us(otRadioCoexMetrics.mAvgRxRequestToGrantTime);
}
else
{
error = OT_ERROR_FAILED;
}
}
// End of CoexMetrics section.
}
#if OTBR_ENABLE_LINK_METRICS_TELEMETRY
{
auto lowPowerMetrics = telemetryData.mutable_low_power_metrics();
// Begin of Link Metrics section.
for (const otNeighborInfo &neighborInfo : neighborTable)
{
otError query_error;
otLinkMetricsValues values;
query_error = otLinkMetricsManagerGetMetricsValueByExtAddr(mInstance, &neighborInfo.mExtAddress, &values);
// Some neighbors don't support Link Metrics Subject feature. So it's expected that some other errors
// are returned.
if (query_error == OT_ERROR_NONE)
{
auto linkMetricsStats = lowPowerMetrics->add_link_metrics_entries();
linkMetricsStats->set_link_margin(values.mLinkMarginValue);
linkMetricsStats->set_rssi(values.mRssiValue);
}
}
}
#endif // OTBR_ENABLE_LINK_METRICS_TELEMETRY
return error;
}
#endif // OTBR_ENABLE_TELEMETRY_DATA_API
otError ThreadHelper::ProcessDatasetForMigration(otOperationalDatasetTlvs &aDatasetTlvs, uint32_t aDelayMilli)
{
otError error = OT_ERROR_NONE;
Tlv *tlv;
timespec currentTime;
uint64_t pendingTimestamp = 0;
VerifyOrExit(FindTlv(OT_MESHCOP_TLV_PENDINGTIMESTAMP, aDatasetTlvs.mTlvs, aDatasetTlvs.mLength) == nullptr,
error = OT_ERROR_INVALID_ARGS);
VerifyOrExit(FindTlv(OT_MESHCOP_TLV_DELAYTIMER, aDatasetTlvs.mTlvs, aDatasetTlvs.mLength) == nullptr,
error = OT_ERROR_INVALID_ARGS);
// There must be sufficient space for a Pending Timestamp TLV and a Delay Timer TLV.
VerifyOrExit(
static_cast<int>(aDatasetTlvs.mLength +
(sizeof(uint8_t) + sizeof(uint8_t) + sizeof(uint64_t)) // Pending Timestamp TLV (10 bytes)
+ (sizeof(uint8_t) + sizeof(uint8_t) + sizeof(uint32_t))) // Delay Timer TLV (6 bytes)
<= int{sizeof(aDatasetTlvs.mTlvs)},
error = OT_ERROR_INVALID_ARGS);
tlv = reinterpret_cast<Tlv *>(aDatasetTlvs.mTlvs + aDatasetTlvs.mLength);
/*
* Pending Timestamp TLV
*
* | Type | Value | Timestamp Seconds | Timestamp Ticks | U bit |
* | 8 | 8 | 48 | 15 | 1 |
*/
tlv->SetType(OT_MESHCOP_TLV_PENDINGTIMESTAMP);
clock_gettime(CLOCK_REALTIME, &currentTime);
pendingTimestamp |= (static_cast<uint64_t>(currentTime.tv_sec) << 16); // Set the 48 bits of Timestamp seconds.
pendingTimestamp |= (((static_cast<uint64_t>(currentTime.tv_nsec) * 32768 / 1000000000) & 0x7fff)
<< 1); // Set the 15 bits of Timestamp ticks, the fractional Unix Time value in 32.768 kHz
// resolution. Leave the U-bit unset.
tlv->SetValue(pendingTimestamp);
tlv = tlv->GetNext();
tlv->SetType(OT_MESHCOP_TLV_DELAYTIMER);
tlv->SetValue(aDelayMilli);
aDatasetTlvs.mLength = reinterpret_cast<uint8_t *>(tlv->GetNext()) - aDatasetTlvs.mTlvs;
exit:
return error;
}
} // namespace agent
} // namespace otbr