Files
openthread/src/cli/cli_dataset.cpp
T
Li Cao 8dadae2ea1 [format] upgrade clang-format to 19 (#11783)
We just updated the version from 14 to 16. But very unfortunately
clang-format-16 was just deprecated recently. So this time we update
the version to the latest available one. There are newer releases than
19 (like 20) but as I tested, sudo apt-get install -y clang-format-19
can work while 20 doesn't work.

misc-include-cleaner in clang-tidy-19 exerts a very strict check which
requires to directly include all headers for every symbols in the
source file. However in our current code we intentionally use some
indirect include. So this commit disables misc-include-cleaner.
2025-08-06 10:49:52 -07:00

1370 lines
37 KiB
C++

/*
* 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 the CLI interpreter.
*/
#include "cli_dataset.hpp"
#include <stdio.h>
#include <stdlib.h>
#include <openthread/dataset.h>
#include <openthread/dataset_ftd.h>
#include <openthread/dataset_updater.h>
#include "cli/cli.hpp"
namespace ot {
namespace Cli {
otOperationalDatasetTlvs Dataset::sDatasetTlvs;
const Dataset::ComponentMapper *Dataset::LookupMapper(const char *aName) const
{
static constexpr ComponentMapper kMappers[] = {
{
"activetimestamp",
&Components::mIsActiveTimestampPresent,
&Dataset::OutputActiveTimestamp,
&Dataset::ParseActiveTimestamp,
},
{
"channel",
&Components::mIsChannelPresent,
&Dataset::OutputChannel,
&Dataset::ParseChannel,
},
{
"channelmask",
&Components::mIsChannelMaskPresent,
&Dataset::OutputChannelMask,
&Dataset::ParseChannelMask,
},
{
"delay",
&Components::mIsDelayPresent,
&Dataset::OutputDelay,
&Dataset::ParseDelay,
},
{
"delaytimer", // Alias for "delay "to ensure backward compatibility for "mgmtsetcommand" command
&Components::mIsDelayPresent,
&Dataset::OutputDelay,
&Dataset::ParseDelay,
},
{
"extpanid",
&Components::mIsExtendedPanIdPresent,
&Dataset::OutputExtendedPanId,
&Dataset::ParseExtendedPanId,
},
{
"localprefix", // Alias for "meshlocalprefix" to ensure backward compatibility in "mgmtsetcommand" command
&Components::mIsMeshLocalPrefixPresent,
&Dataset::OutputMeshLocalPrefix,
&Dataset::ParseMeshLocalPrefix,
},
{
"meshlocalprefix",
&Components::mIsMeshLocalPrefixPresent,
&Dataset::OutputMeshLocalPrefix,
&Dataset::ParseMeshLocalPrefix,
},
{
"networkkey",
&Components::mIsNetworkKeyPresent,
&Dataset::OutputNetworkKey,
&Dataset::ParseNetworkKey,
},
{
"networkname",
&Components::mIsNetworkNamePresent,
&Dataset::OutputNetworkName,
&Dataset::ParseNetworkName,
},
{
"panid",
&Components::mIsPanIdPresent,
&Dataset::OutputPanId,
&Dataset::ParsePanId,
},
{
"pendingtimestamp",
&Components::mIsPendingTimestampPresent,
&Dataset::OutputPendingTimestamp,
&Dataset::ParsePendingTimestamp,
},
{
"pskc",
&Components::mIsPskcPresent,
&Dataset::OutputPskc,
&Dataset::ParsePskc,
},
{
"securitypolicy",
&Components::mIsSecurityPolicyPresent,
&Dataset::OutputSecurityPolicy,
&Dataset::ParseSecurityPolicy,
},
{
"wakeupchannel",
&Components::mIsWakeupChannelPresent,
&Dataset::OutputWakeupChannel,
&Dataset::ParseWakeupChannel,
},
};
static_assert(BinarySearch::IsSorted(kMappers), "kMappers is not sorted");
return BinarySearch::Find(aName, kMappers);
}
//---------------------------------------------------------------------------------------------------------------------
/**
* @cli dataset activetimestamp (get, set)
* @code
* dataset activetimestamp
* 123456789
* Done
* @endcode
* @code
* dataset activetimestamp 123456789
* Done
* @endcode
* @cparam dataset activetimestamp [@ca{timestamp}]
* Pass the optional `timestamp` argument to set the active timestamp.
* @par
* Gets or sets #otOperationalDataset::mActiveTimestamp.
*/
void Dataset::OutputActiveTimestamp(const otOperationalDataset &aDataset)
{
OutputUint64Line(aDataset.mActiveTimestamp.mSeconds);
}
/**
* @cli dataset channel (get,set)
* @code
* dataset channel
* 12
* Done
* @endcode
* @code
* dataset channel 12
* Done
* @endcode
* @cparam dataset channel [@ca{channel-num}]
* Use the optional `channel-num` argument to set the channel.
* @par
* Gets or sets #otOperationalDataset::mChannel.
*/
void Dataset::OutputChannel(const otOperationalDataset &aDataset) { OutputLine("%u", aDataset.mChannel); }
/**
* @cli dataset wakeupchannel (get,set)
* @code
* dataset wakeupchannel
* 13
* Done
* @endcode
* @code
* dataset wakeupchannel 13
* Done
* @endcode
* @cparam dataset wakeupchannel [@ca{channel-num}]
* Use the optional `channel-num` argument to set the wake-up channel.
* @par
* Gets or sets #otOperationalDataset::mWakeupChannel.
*/
void Dataset::OutputWakeupChannel(const otOperationalDataset &aDataset) { OutputLine("%u", aDataset.mWakeupChannel); }
/**
* @cli dataset channelmask (get,set)
* @code
* dataset channelmask
* 0x07fff800
* Done
* @endcode
* @code
* dataset channelmask 0x07fff800
* Done
* @endcode
* @cparam dataset channelmask [@ca{channel-mask}]
* Use the optional `channel-mask` argument to set the channel mask.
* @par
* Gets or sets #otOperationalDataset::mChannelMask
*/
void Dataset::OutputChannelMask(const otOperationalDataset &aDataset)
{
OutputLine("0x%08lx", ToUlong(aDataset.mChannelMask));
}
/**
* @cli dataset delay (get,set)
* @code
* dataset delay
* 1000
* Done
* @endcode
* @code
* dataset delay 1000
* Done
* @endcode
* @cparam dataset delay [@ca{delay}]
* Use the optional `delay` argument to set the delay timer value.
* @par
* Gets or sets #otOperationalDataset::mDelay.
* @sa otDatasetSetDelayTimerMinimal
*/
void Dataset::OutputDelay(const otOperationalDataset &aDataset) { OutputLine("%lu", ToUlong(aDataset.mDelay)); }
/**
* @cli dataset extpanid (get,set)
* @code
* dataset extpanid
* 000db80123456789
* Done
* @endcode
* @code
* dataset extpanid 000db80123456789
* Done
* @endcode
* @cparam dataset extpanid [@ca{extpanid}]
* Use the optional `extpanid` argument to set the Extended Personal Area Network ID.
* @par
* Gets or sets #otOperationalDataset::mExtendedPanId.
* @note The commissioning credential in the dataset buffer becomes stale after changing
* this value. Use `dataset pskc` to reset.
* @csa{dataset pskc (get,set)}
*/
void Dataset::OutputExtendedPanId(const otOperationalDataset &aDataset) { OutputBytesLine(aDataset.mExtendedPanId.m8); }
/**
* @cli dataset meshlocalprefix (get,set)
* @code
* dataset meshlocalprefix
* fd00:db8:0:0::/64
* Done
* @endcode
* @code
* dataset meshlocalprefix fd00:db8:0:0::
* Done
* @endcode
* @cparam dataset meshlocalprefix [@ca{meshlocalprefix}]
* Use the optional `meshlocalprefix` argument to set the Mesh-Local Prefix.
* @par
* Gets or sets #otOperationalDataset::mMeshLocalPrefix.
*/
void Dataset::OutputMeshLocalPrefix(const otOperationalDataset &aDataset)
{
OutputIp6PrefixLine(aDataset.mMeshLocalPrefix);
}
/**
* @cli dataset networkkey (get,set)
* @code
* dataset networkkey
* 00112233445566778899aabbccddeeff
* Done
* @endcode
* @code
* dataset networkkey 00112233445566778899aabbccddeeff
* Done
* @endcode
* @cparam dataset networkkey [@ca{key}]
* Use the optional `key` argument to set the Network Key.
* @par
* Gets or sets #otOperationalDataset::mNetworkKey.
*/
void Dataset::OutputNetworkKey(const otOperationalDataset &aDataset) { OutputBytesLine(aDataset.mNetworkKey.m8); }
/**
* @cli dataset networkname (get,set)
* @code
* dataset networkname
* OpenThread
* Done
* @endcode
* @code
* dataset networkname OpenThread
* Done
* @endcode
* @cparam dataset networkname [@ca{name}]
* Use the optional `name` argument to set the Network Name.
* @par
* Gets or sets #otOperationalDataset::mNetworkName.
* @note The Commissioning Credential in the dataset buffer becomes stale after changing this value.
* Use `dataset pskc` to reset.
* @csa{dataset pskc (get,set)}
*/
void Dataset::OutputNetworkName(const otOperationalDataset &aDataset) { OutputLine("%s", aDataset.mNetworkName.m8); }
/**
* @cli dataset panid (get,set)
* @code
* dataset panid
* 0x1234
* Done
* @endcode
* @code
* dataset panid 0x1234
* Done
* @endcode
* @cparam dataset panid [@ca{panid}]
* Use the optional `panid` argument to set the PAN ID.
* @par
* Gets or sets #otOperationalDataset::mPanId.
*/
void Dataset::OutputPanId(const otOperationalDataset &aDataset) { OutputLine("0x%04x", aDataset.mPanId); }
/**
* @cli dataset pendingtimestamp (get,set)
* @code
* dataset pendingtimestamp
* 123456789
* Done
* @endcode
* @code
* dataset pendingtimestamp 123456789
* Done
* @endcode
* @cparam dataset pendingtimestamp [@ca{timestamp}]
* Use the optional `timestamp` argument to set the pending timestamp seconds.
* @par
* Gets or sets #otOperationalDataset::mPendingTimestamp.
*/
void Dataset::OutputPendingTimestamp(const otOperationalDataset &aDataset)
{
OutputUint64Line(aDataset.mPendingTimestamp.mSeconds);
}
/**
* @cli dataset pskc (get,set)
* @code
* dataset pskc
* 67c0c203aa0b042bfb5381c47aef4d9e
* Done
* @endcode
* @code
* dataset pskc -p 123456
* Done
* @endcode
* @code
* dataset pskc 67c0c203aa0b042bfb5381c47aef4d9e
* Done
* @endcode
* @cparam dataset pskc [@ca{-p} @ca{passphrase}] | [@ca{key}]
* For FTD only, use `-p` with the `passphrase` argument. `-p` generates a pskc from
* the UTF-8 encoded `passphrase` that you provide, together with
* the network name and extended PAN ID. If set, `-p` uses the dataset buffer;
* otherwise, it uses the current stack.
* Alternatively, you can set pskc as `key` (hex format).
* @par
* Gets or sets #otOperationalDataset::mPskc.
*/
void Dataset::OutputPskc(const otOperationalDataset &aDataset) { OutputBytesLine(aDataset.mPskc.m8); }
/**
* @cli dataset securitypolicy (get,set)
* @code
* dataset securitypolicy
* 672 onrc
* Done
* @endcode
* @code
* dataset securitypolicy 672 onrc
* Done
* @endcode
* @cparam dataset securitypolicy [@ca{rotationtime} [@ca{onrcCepR}]]
* * Use `rotationtime` for `thrKeyRotation`, in units of hours.
* * Security Policy commands use the `onrcCepR` argument mappings to get and set
* #otSecurityPolicy members, for example `o` represents
* #otSecurityPolicy::mObtainNetworkKeyEnabled.
* @moreinfo{@dataset}.
* @par
* Gets or sets the %Dataset security policy.
*/
void Dataset::OutputSecurityPolicy(const otOperationalDataset &aDataset)
{
OutputSecurityPolicy(aDataset.mSecurityPolicy);
}
//---------------------------------------------------------------------------------------------------------------------
otError Dataset::ParseActiveTimestamp(Arg *&aArgs, otOperationalDataset &aDataset)
{
otError error;
SuccessOrExit(error = aArgs++->ParseAsUint64(aDataset.mActiveTimestamp.mSeconds));
aDataset.mActiveTimestamp.mTicks = 0;
aDataset.mActiveTimestamp.mAuthoritative = false;
exit:
return error;
}
otError Dataset::ParseChannel(Arg *&aArgs, otOperationalDataset &aDataset)
{
return aArgs++->ParseAsUint16(aDataset.mChannel);
}
otError Dataset::ParseWakeupChannel(Arg *&aArgs, otOperationalDataset &aDataset)
{
return aArgs++->ParseAsUint16(aDataset.mWakeupChannel);
}
otError Dataset::ParseChannelMask(Arg *&aArgs, otOperationalDataset &aDataset)
{
return aArgs++->ParseAsUint32(aDataset.mChannelMask);
}
otError Dataset::ParseDelay(Arg *&aArgs, otOperationalDataset &aDataset)
{
return aArgs++->ParseAsUint32(aDataset.mDelay);
}
otError Dataset::ParseExtendedPanId(Arg *&aArgs, otOperationalDataset &aDataset)
{
return aArgs++->ParseAsHexString(aDataset.mExtendedPanId.m8);
}
otError Dataset::ParseMeshLocalPrefix(Arg *&aArgs, otOperationalDataset &aDataset)
{
otError error;
otIp6Address prefix;
SuccessOrExit(error = aArgs++->ParseAsIp6Address(prefix));
memcpy(aDataset.mMeshLocalPrefix.m8, prefix.mFields.m8, sizeof(aDataset.mMeshLocalPrefix.m8));
exit:
return error;
}
otError Dataset::ParseNetworkKey(Arg *&aArgs, otOperationalDataset &aDataset)
{
return aArgs++->ParseAsHexString(aDataset.mNetworkKey.m8);
}
otError Dataset::ParseNetworkName(Arg *&aArgs, otOperationalDataset &aDataset)
{
otError error = OT_ERROR_NONE;
VerifyOrExit(!aArgs->IsEmpty(), error = OT_ERROR_INVALID_ARGS);
error = otNetworkNameFromString(&aDataset.mNetworkName, aArgs++->GetCString());
exit:
return error;
}
otError Dataset::ParsePanId(Arg *&aArgs, otOperationalDataset &aDataset)
{
return aArgs++->ParseAsUint16(aDataset.mPanId);
}
otError Dataset::ParsePendingTimestamp(Arg *&aArgs, otOperationalDataset &aDataset)
{
otError error;
SuccessOrExit(error = aArgs++->ParseAsUint64(aDataset.mPendingTimestamp.mSeconds));
aDataset.mPendingTimestamp.mTicks = 0;
aDataset.mPendingTimestamp.mAuthoritative = false;
exit:
return error;
}
otError Dataset::ParsePskc(Arg *&aArgs, otOperationalDataset &aDataset)
{
otError error;
#if OPENTHREAD_FTD
if (*aArgs == "-p")
{
aArgs++;
VerifyOrExit(!aArgs->IsEmpty(), error = OT_ERROR_INVALID_ARGS);
SuccessOrExit(error = otDatasetGeneratePskc(
aArgs->GetCString(),
(aDataset.mComponents.mIsNetworkNamePresent
? &aDataset.mNetworkName
: reinterpret_cast<const otNetworkName *>(otThreadGetNetworkName(GetInstancePtr()))),
(aDataset.mComponents.mIsExtendedPanIdPresent ? &aDataset.mExtendedPanId
: otThreadGetExtendedPanId(GetInstancePtr())),
&aDataset.mPskc));
aArgs++;
}
else
#endif
{
ExitNow(error = aArgs++->ParseAsHexString(aDataset.mPskc.m8));
}
exit:
return error;
}
otError Dataset::ParseSecurityPolicy(Arg *&aArgs, otOperationalDataset &aDataset)
{
return ParseSecurityPolicy(aDataset.mSecurityPolicy, aArgs);
}
otError Dataset::ParseTlvs(Arg &aArg, otOperationalDatasetTlvs &aDatasetTlvs)
{
otError error;
uint16_t length;
length = sizeof(aDatasetTlvs.mTlvs);
SuccessOrExit(error = aArg.ParseAsHexString(length, aDatasetTlvs.mTlvs));
aDatasetTlvs.mLength = static_cast<uint8_t>(length);
exit:
return error;
}
//---------------------------------------------------------------------------------------------------------------------
otError Dataset::ProcessCommand(const ComponentMapper &aMapper, Arg aArgs[])
{
otError error = OT_ERROR_NONE;
otOperationalDataset dataset;
if (aArgs[0].IsEmpty())
{
SuccessOrExit(error = otDatasetParseTlvs(&sDatasetTlvs, &dataset));
if (dataset.mComponents.*aMapper.mIsPresentPtr)
{
(this->*aMapper.mOutput)(dataset);
}
}
else
{
ClearAllBytes(dataset);
SuccessOrExit(error = (this->*aMapper.mParse)(aArgs, dataset));
dataset.mComponents.*aMapper.mIsPresentPtr = true;
SuccessOrExit(error = otDatasetUpdateTlvs(&dataset, &sDatasetTlvs));
}
exit:
return error;
}
otError Dataset::Print(otOperationalDatasetTlvs &aDatasetTlvs, bool aNonsensitiveOnly)
{
struct ComponentTitle
{
const char *mTitle; // Title to output.
const char *mName; // To use with `LookupMapper()`.
bool mIsSensitive; // Whether the field is sensitive.
};
static const ComponentTitle kTitles[] = {
{"Pending Timestamp", "pendingtimestamp", false},
{"Active Timestamp", "activetimestamp", false},
{"Channel", "channel", false},
{"Wake-up Channel", "wakeupchannel", false},
{"Channel Mask", "channelmask", false},
{"Delay", "delay", false},
{"Ext PAN ID", "extpanid", false},
{"Mesh Local Prefix", "meshlocalprefix", false},
{"Network Key", "networkkey", true},
{"Network Name", "networkname", false},
{"PAN ID", "panid", false},
{"PSKc", "pskc", true},
{"Security Policy", "securitypolicy", false},
};
otError error;
otOperationalDataset dataset;
SuccessOrExit(error = otDatasetParseTlvs(&aDatasetTlvs, &dataset));
for (const ComponentTitle &title : kTitles)
{
const ComponentMapper *mapper;
mapper = LookupMapper(title.mName);
if (dataset.mComponents.*mapper->mIsPresentPtr)
{
OutputFormat("%s: ", title.mTitle);
if (aNonsensitiveOnly && title.mIsSensitive)
{
OutputLine("[Redacted]");
}
else
{
(this->*mapper->mOutput)(dataset);
}
}
}
exit:
return error;
}
/**
* @cli dataset init (active,new,pending,tlvs)
* @code
* dataset init new
* Done
* @endcode
* @cparam dataset init {@ca{active}|@ca{new}|@ca{pending}|@ca{tlvs}} [@ca{hex-encoded-tlvs}]
* Use `new` to initialize a new dataset, then enter the command `dataset commit active`.
* Use `tlvs` for hex-encoded TLVs.
* @par
* OT CLI checks for `active`, `pending`, or `tlvs` and returns the corresponding values. Otherwise,
* OT CLI creates a new, random network and returns a new dataset.
* @csa{dataset commit active}
* @csa{dataset active}
*/
template <> otError Dataset::Process<Cmd("init")>(Arg aArgs[])
{
otError error = OT_ERROR_INVALID_ARGS;
if (aArgs[0] == "active")
{
error = otDatasetGetActiveTlvs(GetInstancePtr(), &sDatasetTlvs);
}
else if (aArgs[0] == "pending")
{
error = otDatasetGetPendingTlvs(GetInstancePtr(), &sDatasetTlvs);
}
#if OPENTHREAD_FTD
else if (aArgs[0] == "new")
{
otOperationalDataset dataset;
SuccessOrExit(error = otDatasetCreateNewNetwork(GetInstancePtr(), &dataset));
otDatasetConvertToTlvs(&dataset, &sDatasetTlvs);
}
#endif
else if (aArgs[0] == "tlvs")
{
ExitNow(error = ParseTlvs(aArgs[1], sDatasetTlvs));
}
exit:
return error;
}
/**
* @cli dataset active
* @code
* dataset active
* Active Timestamp: 1
* Channel: 13
* Channel Mask: 0x07fff800
* Ext PAN ID: d63e8e3e495ebbc3
* Mesh Local Prefix: fd3d:b50b:f96d:722d::/64
* Network Key: dfd34f0f05cad978ec4e32b0413038ff
* Network Name: OpenThread-8f28
* PAN ID: 0x8f28
* PSKc: c23a76e98f1a6483639b1ac1271e2e27
* Security Policy: 0, onrcb
* Done
* @endcode
* @code
* dataset active -x
* 0e08000000000001000000030000103506000...3023d82c841eff0e68db86f35740c030000ff
* Done
* @endcode
* @code
* dataset active -ns
* Active Timestamp: 1
* Channel: 13
* Channel Mask: 0x07fff800
* Ext PAN ID: d63e8e3e495ebbc3
* Mesh Local Prefix: fd3d:b50b:f96d:722d::/64
* Network Key: [Redacted]
* Network Name: OpenThread-8f28
* PAN ID: 0x8f28
* PSKc: [Redacted]
* Security Policy: 0, onrcb
* Done
* @endcode
* @cparam dataset active [-x|-ns]
* * The optional `-x` argument prints the Active Operational Dataset values as hex-encoded TLVs.
* * The optional `-ns` argument prints the Active Operational Dataset values and redact the sensitive values, including
* the network key and PSKc fields.
* @par api_copy
* #otDatasetGetActive
* @par
* OT CLI uses #otOperationalDataset members to return dataset values to the console.
*/
template <> otError Dataset::Process<Cmd("active")>(Arg aArgs[])
{
otError error;
otOperationalDatasetTlvs dataset;
SuccessOrExit(error = otDatasetGetActiveTlvs(GetInstancePtr(), &dataset));
if (aArgs[0].IsEmpty())
{
error = Print(dataset, /* aNonsensitiveOnly */ false);
}
else if (aArgs[0] == "-x")
{
OutputBytesLine(dataset.mTlvs, dataset.mLength);
}
else if (aArgs[0] == "-ns")
{
error = Print(dataset, /* aNonsensitiveOnly */ true);
}
else
{
error = OT_ERROR_INVALID_ARGS;
}
exit:
return error;
}
template <> otError Dataset::Process<Cmd("pending")>(Arg aArgs[])
{
otError error;
otOperationalDatasetTlvs datasetTlvs;
SuccessOrExit(error = otDatasetGetPendingTlvs(GetInstancePtr(), &datasetTlvs));
if (aArgs[0].IsEmpty())
{
error = Print(datasetTlvs, /* aNonsensitiveOnly */ false);
}
else if (aArgs[0] == "-x")
{
OutputBytesLine(datasetTlvs.mTlvs, datasetTlvs.mLength);
}
else if (aArgs[0] == "-ns")
{
error = Print(datasetTlvs, /* aNonsensitiveOnly */ true);
}
else
{
error = OT_ERROR_INVALID_ARGS;
}
exit:
return error;
}
/**
* @cli dataset clear
* @code
* dataset clear
* Done
* @endcode
* @par
* Reset the Operational %Dataset buffer.
*/
template <> otError Dataset::Process<Cmd("clear")>(Arg aArgs[])
{
OT_UNUSED_VARIABLE(aArgs);
ClearAllBytes(sDatasetTlvs);
return OT_ERROR_NONE;
}
template <> otError Dataset::Process<Cmd("commit")>(Arg aArgs[])
{
otError error = OT_ERROR_INVALID_ARGS;
/**
* @cli dataset commit active
* @code
* dataset commit active
* Done
* @endcode
* @par
* Commit the Operational %Dataset buffer to Active Operational %Dataset.
* @csa{dataset commit pending}
* @sa #otDatasetSetPending
*/
if (aArgs[0] == "active")
{
error = otDatasetSetActiveTlvs(GetInstancePtr(), &sDatasetTlvs);
}
/**
* @cli dataset commit pending
* @code
* dataset commit pending
* Done
* @endcode
* @par
* Commit the Operational %Dataset buffer to Pending Operational %Dataset.
* @csa{dataset commit active}
* @sa #otDatasetSetActive
*/
else if (aArgs[0] == "pending")
{
error = otDatasetSetPendingTlvs(GetInstancePtr(), &sDatasetTlvs);
}
return error;
}
template <> otError Dataset::Process<Cmd("mgmtsetcommand")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
otOperationalDataset dataset;
otOperationalDatasetTlvs tlvs;
ClearAllBytes(dataset);
ClearAllBytes(tlvs);
for (Arg *arg = &aArgs[1]; !arg->IsEmpty();)
{
const ComponentMapper *mapper = LookupMapper(arg->GetCString());
if (mapper != nullptr)
{
arg++;
SuccessOrExit(error = (this->*mapper->mParse)(arg, dataset));
dataset.mComponents.*mapper->mIsPresentPtr = true;
}
else if (*arg == "-x")
{
arg++;
SuccessOrExit(error = ParseTlvs(*arg, tlvs));
arg++;
}
else
{
ExitNow(error = OT_ERROR_INVALID_ARGS);
}
}
/**
* @cli dataset mgmtsetcommand active
* @code
* dataset mgmtsetcommand active activetimestamp 123 securitypolicy 1 onrcb
* Done
* @endcode
* @cparam dataset mgmtsetcommand active [@ca{dataset-components}] [-x @ca{tlv-list}]
* To learn more about these parameters and argument mappings, refer to @dataset.
* @par
* @note This command is primarily used for testing only.
* @par api_copy
* #otDatasetSendMgmtActiveSet
* @csa{dataset mgmtgetcommand active}
* @csa{dataset mgmtgetcommand pending}
* @csa{dataset mgmtsetcommand pending}
*/
if (aArgs[0] == "active")
{
error =
otDatasetSendMgmtActiveSet(GetInstancePtr(), &dataset, tlvs.mTlvs, tlvs.mLength, /* aCallback */ nullptr,
/* aContext */ nullptr);
}
/**
* @cli dataset mgmtsetcommand pending
* @code
* dataset mgmtsetcommand pending activetimestamp 123 securitypolicy 1 onrcb
* Done
* @endcode
* @cparam dataset mgmtsetcommand pending [@ca{dataset-components}] [-x @ca{tlv-list}]
* To learn more about these parameters and argument mappings, refer to @dataset.
* @par
* @note This command is primarily used for testing only.
* @par api_copy
* #otDatasetSendMgmtPendingSet
* @csa{dataset mgmtgetcommand active}
* @csa{dataset mgmtgetcommand pending}
* @csa{dataset mgmtsetcommand active}
*/
else if (aArgs[0] == "pending")
{
error =
otDatasetSendMgmtPendingSet(GetInstancePtr(), &dataset, tlvs.mTlvs, tlvs.mLength, /* aCallback */ nullptr,
/* aContext */ nullptr);
}
else
{
error = OT_ERROR_INVALID_ARGS;
}
exit:
return error;
}
template <> otError Dataset::Process<Cmd("mgmtgetcommand")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
otOperationalDatasetComponents datasetComponents;
otOperationalDatasetTlvs tlvs;
bool destAddrSpecified = false;
otIp6Address address;
ClearAllBytes(datasetComponents);
ClearAllBytes(tlvs);
for (Arg *arg = &aArgs[1]; !arg->IsEmpty(); arg++)
{
const ComponentMapper *mapper = LookupMapper(arg->GetCString());
if (mapper != nullptr)
{
datasetComponents.*mapper->mIsPresentPtr = true;
}
else if (*arg == "-x")
{
arg++;
SuccessOrExit(error = ParseTlvs(*arg, tlvs));
}
else if (*arg == "address")
{
arg++;
SuccessOrExit(error = arg->ParseAsIp6Address(address));
destAddrSpecified = true;
}
else
{
ExitNow(error = OT_ERROR_INVALID_ARGS);
}
}
/**
* @cli dataset mgmtgetcommand active
* @code
* dataset mgmtgetcommand active address fdde:ad00:beef:0:558:f56b:d688:799 activetimestamp securitypolicy
* Done
* @endcode
* @code
* dataset mgmtgetcommand active networkname
* Done
* @endcode
* @cparam dataset mgmtgetcommand active [address @ca{leader-address}] [@ca{dataset-components}] [-x @ca{tlv-list}]
* * Use `address` to specify the IPv6 destination; otherwise, the Leader ALOC is used as default.
* * For `dataset-components`, you can pass any combination of #otOperationalDatasetComponents, for
* example `activetimestamp`, `pendingtimestamp`, or `networkkey`.
* * The optional `-x` argument specifies raw TLVs to be requested.
* @par
* OT CLI sends a MGMT_ACTIVE_GET with the relevant arguments.
* To learn more about these parameters and argument mappings, refer to @dataset.
* @note This command is primarily used for testing only.
* @par api_copy
* #otDatasetSendMgmtActiveGet
* @csa{dataset mgmtgetcommand pending}
* @csa{dataset mgmtsetcommand active}
* @csa{dataset mgmtsetcommand pending}
*/
if (aArgs[0] == "active")
{
error = otDatasetSendMgmtActiveGet(GetInstancePtr(), &datasetComponents, tlvs.mTlvs, tlvs.mLength,
destAddrSpecified ? &address : nullptr);
}
/**
* @cli dataset mgmtgetcommand pending
* @code
* dataset mgmtgetcommand pending address fdde:ad00:beef:0:558:f56b:d688:799 activetimestamp securitypolicy
* Done
* @endcode
* @code
* dataset mgmtgetcommand pending networkname
* Done
* @endcode
* @cparam dataset mgmtgetcommand pending [address @ca{leader-address}] [@ca{dataset-components}] [-x @ca{tlv-list}]
* To learn more about these parameters and argument mappings, refer to @dataset.
* @par
* @note This command is primarily used for testing only.
* @par api_copy
* #otDatasetSendMgmtPendingGet
* @csa{dataset mgmtgetcommand active}
* @csa{dataset mgmtsetcommand active}
* @csa{dataset mgmtsetcommand pending}
*/
else if (aArgs[0] == "pending")
{
error = otDatasetSendMgmtPendingGet(GetInstancePtr(), &datasetComponents, tlvs.mTlvs, tlvs.mLength,
destAddrSpecified ? &address : nullptr);
}
else
{
error = OT_ERROR_INVALID_ARGS;
}
exit:
return error;
}
void Dataset::OutputSecurityPolicy(const otSecurityPolicy &aSecurityPolicy)
{
OutputFormat("%u ", aSecurityPolicy.mRotationTime);
if (aSecurityPolicy.mObtainNetworkKeyEnabled)
{
OutputFormat("o");
}
if (aSecurityPolicy.mNativeCommissioningEnabled)
{
OutputFormat("n");
}
if (aSecurityPolicy.mRoutersEnabled)
{
OutputFormat("r");
}
if (aSecurityPolicy.mExternalCommissioningEnabled)
{
OutputFormat("c");
}
if (aSecurityPolicy.mCommercialCommissioningEnabled)
{
OutputFormat("C");
}
if (aSecurityPolicy.mAutonomousEnrollmentEnabled)
{
OutputFormat("e");
}
if (aSecurityPolicy.mNetworkKeyProvisioningEnabled)
{
OutputFormat("p");
}
if (aSecurityPolicy.mNonCcmRoutersEnabled)
{
OutputFormat("R");
}
OutputLine(" %u", aSecurityPolicy.mVersionThresholdForRouting);
}
otError Dataset::ParseSecurityPolicy(otSecurityPolicy &aSecurityPolicy, Arg *&aArgs)
{
static constexpr uint8_t kMaxVersionThreshold = 7;
otError error;
otSecurityPolicy policy;
uint8_t versionThreshold;
ClearAllBytes(policy);
SuccessOrExit(error = aArgs->ParseAsUint16(policy.mRotationTime));
aArgs++;
VerifyOrExit(!aArgs->IsEmpty());
for (const char *flag = aArgs->GetCString(); *flag != '\0'; flag++)
{
switch (*flag)
{
case 'o':
policy.mObtainNetworkKeyEnabled = true;
break;
case 'n':
policy.mNativeCommissioningEnabled = true;
break;
case 'r':
policy.mRoutersEnabled = true;
break;
case 'c':
policy.mExternalCommissioningEnabled = true;
break;
case 'C':
policy.mCommercialCommissioningEnabled = true;
break;
case 'e':
policy.mAutonomousEnrollmentEnabled = true;
break;
case 'p':
policy.mNetworkKeyProvisioningEnabled = true;
break;
case 'R':
policy.mNonCcmRoutersEnabled = true;
break;
default:
ExitNow(error = OT_ERROR_INVALID_ARGS);
}
}
aArgs++;
VerifyOrExit(!aArgs->IsEmpty());
SuccessOrExit(error = aArgs->ParseAsUint8(versionThreshold));
aArgs++;
VerifyOrExit(versionThreshold <= kMaxVersionThreshold, error = OT_ERROR_INVALID_ARGS);
policy.mVersionThresholdForRouting = versionThreshold;
exit:
if (error == OT_ERROR_NONE)
{
aSecurityPolicy = policy;
}
return error;
}
/**
* @cli dataset set (active,pending)
* @code
* dataset set active 0e08000000000001000000030000103506000...3023d82c841eff0e68db86f35740c030000ff
* Done
* @endcode
* @code
* dataset set pending 0e08000000000001000000030000103506000...3023d82c841eff0e68db86f35740c030000ff
* Done
* @endcode
* @cparam dataset set {active|pending} @ca{tlvs}
* @par
* The CLI `dataset set` command sets the Active Operational %Dataset using hex-encoded TLVs.
* @par api_copy
* #otDatasetSetActive
*/
template <> otError Dataset::Process<Cmd("set")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
otOperationalDatasetTlvs datasetTlvs;
SuccessOrExit(error = ParseTlvs(aArgs[1], datasetTlvs));
if (aArgs[0] == "active")
{
error = otDatasetSetActiveTlvs(GetInstancePtr(), &datasetTlvs);
}
else if (aArgs[0] == "pending")
{
error = otDatasetSetPendingTlvs(GetInstancePtr(), &datasetTlvs);
}
else
{
error = OT_ERROR_INVALID_ARGS;
}
exit:
return error;
}
/**
* @cli dataset tlvs
* @code
* dataset tlvs
* 0e080000000000010000000300001635060004001fffe0020...f7f8
* Done
* @endcode
* @par api_copy
* #otDatasetConvertToTlvs
*/
template <> otError Dataset::Process<Cmd("tlvs")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
OutputBytesLine(sDatasetTlvs.mTlvs, sDatasetTlvs.mLength);
exit:
return error;
}
#if OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE && OPENTHREAD_FTD
template <> otError Dataset::Process<Cmd("updater")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
/**
* @cli dataset updater
* @code
* dataset updater
* Enabled
* Done
* @endcode
* @par api_copy
* #otDatasetUpdaterIsUpdateOngoing
*/
if (aArgs[0].IsEmpty())
{
OutputEnabledDisabledStatus(otDatasetUpdaterIsUpdateOngoing(GetInstancePtr()));
}
/**
* @cli dataset updater start
* @code
* channel
* 19
* Done
* dataset clear
* Done
* dataset channel 15
* Done
* dataset
* Channel: 15
* Done
* dataset updater start
* Done
* dataset updater
* Enabled
* Done
* Dataset update complete: OK
* channel
* 15
* Done
* @endcode
* @par api_copy
* #otDatasetUpdaterRequestUpdate
*/
else if (aArgs[0] == "start")
{
otOperationalDataset dataset;
SuccessOrExit(error = otDatasetParseTlvs(&sDatasetTlvs, &dataset));
SuccessOrExit(
error = otDatasetUpdaterRequestUpdate(GetInstancePtr(), &dataset, &Dataset::HandleDatasetUpdater, this));
}
/**
* @cli dataset updater cancel
* @code
* @dataset updater cancel
* Done
* @endcode
* @par api_copy
* #otDatasetUpdaterCancelUpdate
*/
else if (aArgs[0] == "cancel")
{
otDatasetUpdaterCancelUpdate(GetInstancePtr());
}
else
{
error = OT_ERROR_INVALID_ARGS;
}
exit:
return error;
}
void Dataset::HandleDatasetUpdater(otError aError, void *aContext)
{
static_cast<Dataset *>(aContext)->HandleDatasetUpdater(aError);
}
void Dataset::HandleDatasetUpdater(otError aError)
{
OutputLine("Dataset update complete: %s", otThreadErrorToString(aError));
}
#endif // OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE && OPENTHREAD_FTD
otError Dataset::Process(Arg aArgs[])
{
#define CmdEntry(aCommandString) {aCommandString, &Dataset::Process<Cmd(aCommandString)>}
static constexpr Command kCommands[] = {
CmdEntry("active"), CmdEntry("clear"), CmdEntry("commit"), CmdEntry("init"),
CmdEntry("mgmtgetcommand"), CmdEntry("mgmtsetcommand"), CmdEntry("pending"), CmdEntry("set"),
CmdEntry("tlvs"),
#if OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE && OPENTHREAD_FTD
CmdEntry("updater"),
#endif
};
#undef CmdEntry
static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted");
otError error = OT_ERROR_INVALID_COMMAND;
const Command *command;
const ComponentMapper *mapper;
if (aArgs[0].IsEmpty())
{
ExitNow(error = Print(sDatasetTlvs, /* aNonsensitiveOnly */ false));
}
/**
* @cli dataset help
* @code
* dataset help
* help
* active
* activetimestamp
* channel
* channelmask
* clear
* commit
* delay
* extpanid
* init
* meshlocalprefix
* mgmtgetcommand
* mgmtsetcommand
* networkkey
* networkname
* panid
* pending
* pendingtimestamp
* pskc
* securitypolicy
* set
* tlvs
* Done
* @endcode
* @par
* Gets a list of `dataset` CLI commands. @moreinfo{@dataset}.
*/
if (aArgs[0] == "help")
{
OutputCommandTable(kCommands);
ExitNow(error = OT_ERROR_NONE);
}
mapper = LookupMapper(aArgs[0].GetCString());
if (mapper != nullptr)
{
error = ProcessCommand(*mapper, aArgs + 1);
ExitNow();
}
command = BinarySearch::Find(aArgs[0].GetCString(), kCommands);
VerifyOrExit(command != nullptr);
error = (this->*command->mHandler)(aArgs + 1);
exit:
return error;
}
} // namespace Cli
} // namespace ot