blob: 73866d3c29c1cc523f9862d1666cbeee47798652 [file] [log] [blame]
/*
* Copyright (c) 2016, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file
* This file implements the CLI interpreter.
*/
#include "cli.hpp"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openthread/diag.h>
#include <openthread/dns.h>
#include <openthread/icmp6.h>
#include <openthread/link.h>
#include <openthread/logging.h>
#include <openthread/ncp.h>
#include <openthread/thread.h>
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
#include <openthread/network_time.h>
#endif
#if OPENTHREAD_FTD
#include <openthread/dataset_ftd.h>
#include <openthread/thread_ftd.h>
#endif
#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
#include <openthread/border_router.h>
#endif
#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
#include <openthread/server.h>
#endif
#if OPENTHREAD_CONFIG_CHILD_SUPERVISION_ENABLE
#include <openthread/child_supervision.h>
#endif
#if OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE
#include <openthread/platform/misc.h>
#endif
#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
#include <openthread/backbone_router.h>
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
#include <openthread/backbone_router_ftd.h>
#endif
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE
#include <openthread/link_metrics.h>
#endif
#endif
#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && OPENTHREAD_FTD
#include <openthread/channel_manager.h>
#endif
#if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE
#include <openthread/channel_monitor.h>
#endif
#if (OPENTHREAD_CONFIG_LOG_OUTPUT == OPENTHREAD_CONFIG_LOG_OUTPUT_DEBUG_UART) && OPENTHREAD_POSIX
#include <openthread/platform/debug_uart.h>
#endif
#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
#include <openthread/trel.h>
#endif
#include "common/new.hpp"
#include "common/string.hpp"
#include "mac/channel_mask.hpp"
namespace ot {
namespace Cli {
Interpreter *Interpreter::sInterpreter = nullptr;
static OT_DEFINE_ALIGNED_VAR(sInterpreterRaw, sizeof(Interpreter), uint64_t);
Interpreter::Interpreter(Instance *aInstance, otCliOutputCallback aCallback, void *aContext)
: Output(aInstance, aCallback, aContext)
, mUserCommands(nullptr)
, mUserCommandsLength(0)
, mCommandIsPending(false)
, mTimer(*aInstance, HandleTimer, this)
#if OPENTHREAD_FTD || OPENTHREAD_MTD
#if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE
, mSntpQueryingInProgress(false)
#endif
, mDataset(*this)
, mNetworkData(*this)
, mUdp(*this)
#if OPENTHREAD_CONFIG_TCP_ENABLE && OPENTHREAD_CONFIG_CLI_TCP_ENABLE
, mTcp(*this)
#endif
#if OPENTHREAD_CONFIG_COAP_API_ENABLE
, mCoap(*this)
#endif
#if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE
, mCoapSecure(*this)
#endif
#if OPENTHREAD_CONFIG_COMMISSIONER_ENABLE && OPENTHREAD_FTD
, mCommissioner(*this)
#endif
#if OPENTHREAD_CONFIG_JOINER_ENABLE
, mJoiner(*this)
#endif
#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
, mSrpClient(*this)
#endif
#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
, mSrpServer(*this)
#endif
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
, mHistory(*this)
#endif
#if OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE
, mLocateInProgress(false)
#endif
#endif // OPENTHREAD_FTD || OPENTHREAD_MTD
{
#if OPENTHREAD_FTD
otThreadSetDiscoveryRequestCallback(GetInstancePtr(), &Interpreter::HandleDiscoveryRequest, this);
#endif
OutputPrompt();
}
void Interpreter::OutputResult(otError aError)
{
OT_ASSERT(mCommandIsPending);
VerifyOrExit(aError != OT_ERROR_PENDING);
if (aError == OT_ERROR_NONE)
{
OutputLine("Done");
}
else
{
OutputLine("Error %d: %s", aError, otThreadErrorToString(aError));
}
mCommandIsPending = false;
mTimer.Stop();
OutputPrompt();
exit:
return;
}
const char *Interpreter::LinkModeToString(const otLinkModeConfig &aLinkMode, char (&aStringBuffer)[kLinkModeStringSize])
{
char *flagsPtr = &aStringBuffer[0];
if (aLinkMode.mRxOnWhenIdle)
{
*flagsPtr++ = 'r';
}
if (aLinkMode.mDeviceType)
{
*flagsPtr++ = 'd';
}
if (aLinkMode.mNetworkData)
{
*flagsPtr++ = 'n';
}
if (flagsPtr == &aStringBuffer[0])
{
*flagsPtr++ = '-';
}
*flagsPtr = '\0';
return aStringBuffer;
}
#if OPENTHREAD_CONFIG_DIAG_ENABLE
template <> otError Interpreter::Process<Cmd("diag")>(Arg aArgs[])
{
otError error;
char * args[kMaxArgs];
char output[OPENTHREAD_CONFIG_DIAG_OUTPUT_BUFFER_SIZE];
// all diagnostics related features are processed within diagnostics module
Arg::CopyArgsToStringArray(aArgs, args);
error = otDiagProcessCmd(GetInstancePtr(), Arg::GetArgsLength(aArgs), args, output, sizeof(output));
OutputFormat("%s", output);
return error;
}
#endif
template <> otError Interpreter::Process<Cmd("version")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
if (aArgs[0].IsEmpty())
{
OutputLine("%s", otGetVersionString());
}
else if (aArgs[0] == "api")
{
OutputLine("%d", OPENTHREAD_API_VERSION);
}
else
{
error = OT_ERROR_INVALID_COMMAND;
}
return error;
}
template <> otError Interpreter::Process<Cmd("reset")>(Arg aArgs[])
{
OT_UNUSED_VARIABLE(aArgs);
otInstanceReset(GetInstancePtr());
return OT_ERROR_NONE;
}
void Interpreter::ProcessLine(char *aBuf)
{
Arg args[kMaxArgs + 1];
otError error = OT_ERROR_NONE;
OT_ASSERT(aBuf != nullptr);
// Ignore the command if another command is pending.
VerifyOrExit(!mCommandIsPending, args[0].Clear());
mCommandIsPending = true;
VerifyOrExit(StringLength(aBuf, kMaxLineLength) <= kMaxLineLength - 1, error = OT_ERROR_PARSE);
SuccessOrExit(error = Utils::CmdLineParser::ParseCmd(aBuf, args, kMaxArgs));
VerifyOrExit(!args[0].IsEmpty(), mCommandIsPending = false);
LogInput(args);
#if OPENTHREAD_CONFIG_DIAG_ENABLE
if (otDiagIsEnabled(GetInstancePtr()) && (args[0] != "diag") && (args[0] != "factoryreset"))
{
OutputLine("under diagnostics mode, execute 'diag stop' before running any other commands.");
ExitNow(error = OT_ERROR_INVALID_STATE);
}
#endif
error = ProcessCommand(args);
exit:
if ((error != OT_ERROR_NONE) || !args[0].IsEmpty())
{
OutputResult(error);
}
else if (!mCommandIsPending)
{
OutputPrompt();
}
}
otError Interpreter::ProcessUserCommands(Arg aArgs[])
{
otError error = OT_ERROR_INVALID_COMMAND;
for (uint8_t i = 0; i < mUserCommandsLength; i++)
{
if (aArgs[0] == mUserCommands[i].mName)
{
char *args[kMaxArgs];
Arg::CopyArgsToStringArray(aArgs, args);
mUserCommands[i].mCommand(mUserCommandsContext, Arg::GetArgsLength(aArgs) - 1, args + 1);
error = OT_ERROR_NONE;
break;
}
}
return error;
}
void Interpreter::SetUserCommands(const otCliCommand *aCommands, uint8_t aLength, void *aContext)
{
mUserCommands = aCommands;
mUserCommandsLength = aLength;
mUserCommandsContext = aContext;
}
#if OPENTHREAD_FTD || OPENTHREAD_MTD
otError Interpreter::ParseEnableOrDisable(const Arg &aArg, bool &aEnable)
{
otError error = OT_ERROR_NONE;
if (aArg == "enable")
{
aEnable = true;
}
else if (aArg == "disable")
{
aEnable = false;
}
else
{
error = OT_ERROR_INVALID_COMMAND;
}
return error;
}
otError Interpreter::ParseJoinerDiscerner(Arg &aArg, otJoinerDiscerner &aDiscerner)
{
otError error;
char * separator;
VerifyOrExit(!aArg.IsEmpty(), error = OT_ERROR_INVALID_ARGS);
separator = strstr(aArg.GetCString(), "/");
VerifyOrExit(separator != nullptr, error = OT_ERROR_NOT_FOUND);
SuccessOrExit(error = Utils::CmdLineParser::ParseAsUint8(separator + 1, aDiscerner.mLength));
VerifyOrExit(aDiscerner.mLength > 0 && aDiscerner.mLength <= 64, error = OT_ERROR_INVALID_ARGS);
*separator = '\0';
error = aArg.ParseAsUint64(aDiscerner.mValue);
exit:
return error;
}
#if OPENTHREAD_CONFIG_PING_SENDER_ENABLE
otError Interpreter::ParsePingInterval(const Arg &aArg, uint32_t &aInterval)
{
otError error = OT_ERROR_NONE;
const char * string = aArg.GetCString();
const uint32_t msFactor = 1000;
uint32_t factor = msFactor;
aInterval = 0;
while (*string)
{
if ('0' <= *string && *string <= '9')
{
// In the case of seconds, change the base of already calculated value.
if (factor == msFactor)
{
aInterval *= 10;
}
aInterval += static_cast<uint32_t>(*string - '0') * factor;
// In the case of milliseconds, change the multiplier factor.
if (factor != msFactor)
{
factor /= 10;
}
}
else if (*string == '.')
{
// Accept only one dot character.
VerifyOrExit(factor == msFactor, error = OT_ERROR_INVALID_ARGS);
// Start analyzing hundreds of milliseconds.
factor /= 10;
}
else
{
ExitNow(error = OT_ERROR_INVALID_ARGS);
}
string++;
}
exit:
return error;
}
#endif // OPENTHREAD_CONFIG_PING_SENDER_ENABLE
otError Interpreter::ParsePreference(const Arg &aArg, otRoutePreference &aPreference)
{
otError error = OT_ERROR_NONE;
if (aArg == "high")
{
aPreference = OT_ROUTE_PREFERENCE_HIGH;
}
else if (aArg == "med")
{
aPreference = OT_ROUTE_PREFERENCE_MED;
}
else if (aArg == "low")
{
aPreference = OT_ROUTE_PREFERENCE_LOW;
}
else
{
error = OT_ERROR_INVALID_ARGS;
}
return error;
}
const char *Interpreter::PreferenceToString(signed int aPreference)
{
const char *str = "";
switch (aPreference)
{
case OT_ROUTE_PREFERENCE_LOW:
str = "low";
break;
case OT_ROUTE_PREFERENCE_MED:
str = "med";
break;
case OT_ROUTE_PREFERENCE_HIGH:
str = "high";
break;
default:
break;
}
return str;
}
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
template <> otError Interpreter::Process<Cmd("history")>(Arg aArgs[])
{
return mHistory.Process(aArgs);
}
#endif
#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE
template <> otError Interpreter::Process<Cmd("ba")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
/**
* @cli ba port
* @code
* ba port
* 49153
* Done
* @endcode
* @par api_copy
* #otBorderAgentGetUdpPort
*/
if (aArgs[0] == "port")
{
OutputLine("%hu", otBorderAgentGetUdpPort(GetInstancePtr()));
}
/**
* @cli ba state
* @code
* ba state
* Started
* Done
* @endcode
* @par api_copy
* #otBorderAgentGetState
*/
else if (aArgs[0] == "state")
{
static const char *const kStateStrings[] = {
"Stopped" // (0) OT_BORDER_AGENT_STATE_STOPPED
"Started", // (1) OT_BORDER_AGENT_STATE_STARTED
"Active", // (2) OT_BORDER_AGENT_STATE_ACTIVE
};
static_assert(0 == OT_BORDER_AGENT_STATE_STOPPED, "OT_BORDER_AGENT_STATE_STOPPED value is incorrect");
static_assert(1 == OT_BORDER_AGENT_STATE_STARTED, "OT_BORDER_AGENT_STATE_STARTED value is incorrect");
static_assert(2 == OT_BORDER_AGENT_STATE_ACTIVE, "OT_BORDER_AGENT_STATE_ACTIVE value is incorrect");
OutputLine("%s", Stringify(otBorderAgentGetState(GetInstancePtr()), kStateStrings));
}
else
{
error = OT_ERROR_INVALID_COMMAND;
}
return error;
}
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE
#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
template <> otError Interpreter::Process<Cmd("br")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
bool enable;
/**
* @cli br (enable,disable)
* @code
* br enable
* Done
* @endcode
* @code
* br disable
* Done
* @endcode
* @par api_copy
* #otBorderRoutingSetEnabled
*/
if (ParseEnableOrDisable(aArgs[0], enable) == OT_ERROR_NONE)
{
SuccessOrExit(error = otBorderRoutingSetEnabled(GetInstancePtr(), enable));
}
/**
* @cli br omrprefix
* @code
* br omrprefix
* fdfc:1ff5:1512:5622::/64
* Done
* @endcode
* @par api_copy
* #otBorderRoutingGetOmrPrefix
*/
else if (aArgs[0] == "omrprefix")
{
otIp6Prefix omrPrefix;
SuccessOrExit(error = otBorderRoutingGetOmrPrefix(GetInstancePtr(), &omrPrefix));
OutputIp6PrefixLine(omrPrefix);
}
/**
* @cli br favoredomrprefix
* @code
* br favoredomrprefix
* fdfc:1ff5:1512:5622::/64 prf:low
* Done
* @endcode
* @par api_copy
* #otBorderRoutingGetFavoredOmrPrefix
*/
else if (aArgs[0] == "favoredomrprefix")
{
otIp6Prefix prefix;
otRoutePreference preference;
SuccessOrExit(error = otBorderRoutingGetFavoredOmrPrefix(GetInstancePtr(), &prefix, &preference));
OutputIp6Prefix(prefix);
OutputLine(" prf:%s", PreferenceToString(preference));
}
/**
* @cli br onlinkprefix
* @code
* br onlinkprefix
* fd41:2650:a6f5:0::/64
* Done
* @endcode
* @par api_copy
* #otBorderRoutingGetOnLinkPrefix
*/
else if (aArgs[0] == "onlinkprefix")
{
otIp6Prefix onLinkPrefix;
SuccessOrExit(error = otBorderRoutingGetOnLinkPrefix(GetInstancePtr(), &onLinkPrefix));
OutputIp6PrefixLine(onLinkPrefix);
}
#if OPENTHREAD_CONFIG_BORDER_ROUTING_NAT64_ENABLE
/**
* @cli br nat64prefix
* @code
* br nat64prefix
* fd14:1078:b3d5:b0b0:0:0::/96
* Done
* @endcode
* @par api_copy
* #otBorderRoutingGetNat64Prefix
*/
else if (aArgs[0] == "nat64prefix")
{
otIp6Prefix nat64Prefix;
SuccessOrExit(error = otBorderRoutingGetNat64Prefix(GetInstancePtr(), &nat64Prefix));
OutputIp6PrefixLine(nat64Prefix);
}
#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_NAT64_ENABLE
/**
* @cli br rioprf (high,med,low)
* @code
* br rioprf
* med
* Done
* @endcode
* @code
* br rioprf low
* Done
* @endcode
* @cparam br rioprf [@ca{high}|@ca{med}|@ca{low}]
* @par api_copy
* #otBorderRoutingSetRouteInfoOptionPreference
*
*/
else if (aArgs[0] == "rioprf")
{
if (aArgs[1].IsEmpty())
{
OutputLine("%s", PreferenceToString(otBorderRoutingGetRouteInfoOptionPreference(GetInstancePtr())));
}
else
{
otRoutePreference preference;
SuccessOrExit(error = ParsePreference(aArgs[1], preference));
otBorderRoutingSetRouteInfoOptionPreference(GetInstancePtr(), preference);
}
}
/**
* @cli br prefixtable
* @code
* br prefixtable
* prefix:fd00:1234:5678:0::/64, on-link:no, ms-since-rx:29526, lifetime:1800, route-prf:med,
* router:ff02:0:0:0:0:0:0:1
* prefix:1200:abba:baba:0::/64, on-link:yes, ms-since-rx:29527, lifetime:1800, preferred:1800,
* router:ff02:0:0:0:0:0:0:1
* Done
* @endcode
* @par api_copy
* #otBorderRoutingGetNextPrefixTableEntry
*
*/
else if (aArgs[0] == "prefixtable")
{
otBorderRoutingPrefixTableIterator iterator;
otBorderRoutingPrefixTableEntry entry;
otBorderRoutingPrefixTableInitIterator(GetInstancePtr(), &iterator);
while (otBorderRoutingGetNextPrefixTableEntry(GetInstancePtr(), &iterator, &entry) == OT_ERROR_NONE)
{
char string[OT_IP6_PREFIX_STRING_SIZE];
otIp6PrefixToString(&entry.mPrefix, string, sizeof(string));
OutputFormat("prefix:%s, on-link:%s, ms-since-rx:%u, lifetime:%u, ", string, entry.mIsOnLink ? "yes" : "no",
entry.mMsecSinceLastUpdate, entry.mValidLifetime);
if (entry.mIsOnLink)
{
OutputFormat("preferred:%u, ", entry.mPreferredLifetime);
}
else
{
OutputFormat("route-prf:%s, ", PreferenceToString(entry.mRoutePreference));
}
otIp6AddressToString(&entry.mRouterAddress, string, sizeof(string));
OutputLine("router:%s", string);
}
}
else
{
error = OT_ERROR_INVALID_COMMAND;
}
exit:
return error;
}
#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
template <> otError Interpreter::Process<Cmd("bbr")>(Arg aArgs[])
{
otError error = OT_ERROR_INVALID_COMMAND;
otBackboneRouterConfig config;
if (aArgs[0].IsEmpty())
{
if (otBackboneRouterGetPrimary(GetInstancePtr(), &config) == OT_ERROR_NONE)
{
OutputLine("BBR Primary:");
OutputLine("server16: 0x%04X", config.mServer16);
OutputLine("seqno: %d", config.mSequenceNumber);
OutputLine("delay: %d secs", config.mReregistrationDelay);
OutputLine("timeout: %d secs", config.mMlrTimeout);
}
else
{
OutputLine("BBR Primary: None");
}
error = OT_ERROR_NONE;
}
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
else
{
if (aArgs[0] == "mgmt")
{
if (aArgs[1].IsEmpty())
{
ExitNow(error = OT_ERROR_INVALID_COMMAND);
}
#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_DUA_NDPROXYING_ENABLE && OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
else if (aArgs[1] == "dua")
{
uint8_t status;
otIp6InterfaceIdentifier *mlIid = nullptr;
otIp6InterfaceIdentifier iid;
SuccessOrExit(error = aArgs[2].ParseAsUint8(status));
if (!aArgs[3].IsEmpty())
{
SuccessOrExit(error = aArgs[3].ParseAsHexString(iid.mFields.m8));
mlIid = &iid;
VerifyOrExit(aArgs[4].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
}
otBackboneRouterConfigNextDuaRegistrationResponse(GetInstancePtr(), mlIid, status);
ExitNow();
}
#endif
#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_MULTICAST_ROUTING_ENABLE
else if (aArgs[1] == "mlr")
{
error = ProcessBackboneRouterMgmtMlr(aArgs + 2);
ExitNow();
}
#endif
}
SuccessOrExit(error = ProcessBackboneRouterLocal(aArgs));
}
exit:
#endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
return error;
}
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_MULTICAST_ROUTING_ENABLE
otError Interpreter::ProcessBackboneRouterMgmtMlr(Arg aArgs[])
{
otError error = OT_ERROR_INVALID_COMMAND;
if (aArgs[0] == "listener")
{
if (aArgs[1].IsEmpty())
{
PrintMulticastListenersTable();
ExitNow(error = OT_ERROR_NONE);
}
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
if (aArgs[1] == "clear")
{
otBackboneRouterMulticastListenerClear(GetInstancePtr());
error = OT_ERROR_NONE;
}
else if (aArgs[1] == "add")
{
otIp6Address address;
uint32_t timeout = 0;
SuccessOrExit(error = aArgs[2].ParseAsIp6Address(address));
if (!aArgs[3].IsEmpty())
{
SuccessOrExit(error = aArgs[3].ParseAsUint32(timeout));
VerifyOrExit(aArgs[4].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
}
error = otBackboneRouterMulticastListenerAdd(GetInstancePtr(), &address, timeout);
}
}
else if (aArgs[0] == "response")
{
error = ProcessSet(aArgs + 1, otBackboneRouterConfigNextMulticastListenerRegistrationResponse);
#endif
}
exit:
return error;
}
void Interpreter::PrintMulticastListenersTable(void)
{
otBackboneRouterMulticastListenerIterator iter = OT_BACKBONE_ROUTER_MULTICAST_LISTENER_ITERATOR_INIT;
otBackboneRouterMulticastListenerInfo listenerInfo;
while (otBackboneRouterMulticastListenerGetNext(GetInstancePtr(), &iter, &listenerInfo) == OT_ERROR_NONE)
{
OutputIp6Address(listenerInfo.mAddress);
OutputLine(" %u", listenerInfo.mTimeout);
}
}
#endif // OPENTHREAD_CONFIG_BACKBONE_ROUTER_MULTICAST_ROUTING_ENABLE
otError Interpreter::ProcessBackboneRouterLocal(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
otBackboneRouterConfig config;
bool enable;
if (ParseEnableOrDisable(aArgs[0], enable) == OT_ERROR_NONE)
{
otBackboneRouterSetEnabled(GetInstancePtr(), enable);
}
else if (aArgs[0] == "jitter")
{
error = ProcessGetSet(aArgs + 1, otBackboneRouterGetRegistrationJitter, otBackboneRouterSetRegistrationJitter);
}
else if (aArgs[0] == "register")
{
SuccessOrExit(error = otBackboneRouterRegister(GetInstancePtr()));
}
else if (aArgs[0] == "state")
{
static const char *const kStateStrings[] = {
"Disabled", // (0) OT_BACKBONE_ROUTER_STATE_DISABLED
"Secondary", // (1) OT_BACKBONE_ROUTER_STATE_SECONDARY
"Primary", // (2) OT_BACKBONE_ROUTER_STATE_PRIMARY
};
static_assert(0 == OT_BACKBONE_ROUTER_STATE_DISABLED, "OT_BACKBONE_ROUTER_STATE_DISABLED value is incorrect");
static_assert(1 == OT_BACKBONE_ROUTER_STATE_SECONDARY, "OT_BACKBONE_ROUTER_STATE_SECONDARY value is incorrect");
static_assert(2 == OT_BACKBONE_ROUTER_STATE_PRIMARY, "OT_BACKBONE_ROUTER_STATE_PRIMARY value is incorrect");
OutputLine("%s", Stringify(otBackboneRouterGetState(GetInstancePtr()), kStateStrings));
}
else if (aArgs[0] == "config")
{
otBackboneRouterGetConfig(GetInstancePtr(), &config);
if (aArgs[1].IsEmpty())
{
OutputLine("seqno: %d", config.mSequenceNumber);
OutputLine("delay: %d secs", config.mReregistrationDelay);
OutputLine("timeout: %d secs", config.mMlrTimeout);
}
else
{
// Set local Backbone Router configuration.
for (Arg *arg = &aArgs[1]; !arg->IsEmpty(); arg++)
{
if (*arg == "seqno")
{
arg++;
SuccessOrExit(error = arg->ParseAsUint8(config.mSequenceNumber));
}
else if (*arg == "delay")
{
arg++;
SuccessOrExit(error = arg->ParseAsUint16(config.mReregistrationDelay));
}
else if (*arg == "timeout")
{
arg++;
SuccessOrExit(error = arg->ParseAsUint32(config.mMlrTimeout));
}
else
{
ExitNow(error = OT_ERROR_INVALID_ARGS);
}
}
SuccessOrExit(error = otBackboneRouterSetConfig(GetInstancePtr(), &config));
}
}
else
{
error = OT_ERROR_INVALID_COMMAND;
}
exit:
return error;
}
#endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
template <> otError Interpreter::Process<Cmd("domainname")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
if (aArgs[0].IsEmpty())
{
OutputLine("%s", otThreadGetDomainName(GetInstancePtr()));
}
else
{
SuccessOrExit(error = otThreadSetDomainName(GetInstancePtr(), aArgs[0].GetCString()));
}
exit:
return error;
}
#if OPENTHREAD_CONFIG_DUA_ENABLE
template <> otError Interpreter::Process<Cmd("dua")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
if (aArgs[0] == "iid")
{
if (aArgs[1].IsEmpty())
{
const otIp6InterfaceIdentifier *iid = otThreadGetFixedDuaInterfaceIdentifier(GetInstancePtr());
if (iid != nullptr)
{
OutputBytesLine(iid->mFields.m8);
}
}
else if (aArgs[1] == "clear")
{
error = otThreadSetFixedDuaInterfaceIdentifier(GetInstancePtr(), nullptr);
}
else
{
otIp6InterfaceIdentifier iid;
SuccessOrExit(error = aArgs[1].ParseAsHexString(iid.mFields.m8));
error = otThreadSetFixedDuaInterfaceIdentifier(GetInstancePtr(), &iid);
}
}
else
{
error = OT_ERROR_INVALID_COMMAND;
}
exit:
return error;
}
#endif // OPENTHREAD_CONFIG_DUA_ENABLE
#endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
template <> otError Interpreter::Process<Cmd("bufferinfo")>(Arg aArgs[])
{
OT_UNUSED_VARIABLE(aArgs);
struct BufferInfoName
{
const otMessageQueueInfo otBufferInfo::*mQueuePtr;
const char * mName;
};
static const BufferInfoName kBufferInfoNames[] = {
{&otBufferInfo::m6loSendQueue, "6lo send"},
{&otBufferInfo::m6loReassemblyQueue, "6lo reas"},
{&otBufferInfo::mIp6Queue, "ip6"},
{&otBufferInfo::mMplQueue, "mpl"},
{&otBufferInfo::mMleQueue, "mle"},
{&otBufferInfo::mCoapQueue, "coap"},
{&otBufferInfo::mCoapSecureQueue, "coap secure"},
{&otBufferInfo::mApplicationCoapQueue, "application coap"},
};
otBufferInfo bufferInfo;
otMessageGetBufferInfo(GetInstancePtr(), &bufferInfo);
OutputLine("total: %d", bufferInfo.mTotalBuffers);
OutputLine("free: %d", bufferInfo.mFreeBuffers);
for (const BufferInfoName &info : kBufferInfoNames)
{
OutputLine("%s: %u %u %u", info.mName, (bufferInfo.*info.mQueuePtr).mNumMessages,
(bufferInfo.*info.mQueuePtr).mNumBuffers, (bufferInfo.*info.mQueuePtr).mTotalBytes);
}
return OT_ERROR_NONE;
}
template <> otError Interpreter::Process<Cmd("ccathreshold")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
int8_t cca;
if (aArgs[0].IsEmpty())
{
SuccessOrExit(error = otPlatRadioGetCcaEnergyDetectThreshold(GetInstancePtr(), &cca));
OutputLine("%d dBm", cca);
}
else
{
SuccessOrExit(error = aArgs[0].ParseAsInt8(cca));
error = otPlatRadioSetCcaEnergyDetectThreshold(GetInstancePtr(), cca);
}
exit:
return error;
}
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
template <> otError Interpreter::Process<Cmd("ccm")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
bool enable;
VerifyOrExit(!aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_COMMAND);
SuccessOrExit(error = ParseEnableOrDisable(aArgs[0], enable));
otThreadSetCcmEnabled(GetInstancePtr(), enable);
exit:
return error;
}
template <> otError Interpreter::Process<Cmd("tvcheck")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
bool enable;
VerifyOrExit(!aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_COMMAND);
SuccessOrExit(error = ParseEnableOrDisable(aArgs[0], enable));
otThreadSetThreadVersionCheckEnabled(GetInstancePtr(), enable);
exit:
return error;
}
#endif
/**
* @cli channel (get,set)
* @code
* channel
* 11
* Done
* @endcode
* @code
* channel 11
* Done
* @endcode
* @cparam channel [@ca{channel-num}]
* Use `channel-num` to set the channel.
* @par
* Gets or sets the IEEE 802.15.4 Channel value.
*/
template <> otError Interpreter::Process<Cmd("channel")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
/**
* @cli channel supported
* @code
* channel supported
* 0x7fff800
* Done
* @endcode
* @par api_copy
* #otPlatRadioGetSupportedChannelMask
*/
if (aArgs[0] == "supported")
{
OutputLine("0x%x", otPlatRadioGetSupportedChannelMask(GetInstancePtr()));
}
/**
* @cli channel preferred
* @code
* channel preferred
* 0x7fff800
* Done
* @endcode
* @par api_copy
* #otPlatRadioGetPreferredChannelMask
*/
else if (aArgs[0] == "preferred")
{
OutputLine("0x%x", otPlatRadioGetPreferredChannelMask(GetInstancePtr()));
}
#if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE
/**
* @cli channel monitor
* @code
* channel monitor
* enabled: 1
* interval: 41000
* threshold: -75
* window: 960
* count: 10552
* occupancies:
* ch 11 (0x0cb7) 4.96% busy
* ch 12 (0x2e2b) 18.03% busy
* ch 13 (0x2f54) 18.48% busy
* ch 14 (0x0fef) 6.22% busy
* ch 15 (0x1536) 8.28% busy
* ch 16 (0x1746) 9.09% busy
* ch 17 (0x0b8b) 4.50% busy
* ch 18 (0x60a7) 37.75% busy
* ch 19 (0x0810) 3.14% busy
* ch 20 (0x0c2a) 4.75% busy
* ch 21 (0x08dc) 3.46% busy
* ch 22 (0x101d) 6.29% busy
* ch 23 (0x0092) 0.22% busy
* ch 24 (0x0028) 0.06% busy
* ch 25 (0x0063) 0.15% busy
* ch 26 (0x058c) 2.16% busy
* Done
* @endcode
* @par
* Get the current channel monitor state and channel occupancy.
* `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` is required.
*/
else if (aArgs[0] == "monitor")
{
if (aArgs[1].IsEmpty())
{
OutputLine("enabled: %d", otChannelMonitorIsEnabled(GetInstancePtr()));
if (otChannelMonitorIsEnabled(GetInstancePtr()))
{
uint32_t channelMask = otLinkGetSupportedChannelMask(GetInstancePtr());
uint8_t channelNum = sizeof(channelMask) * CHAR_BIT;
OutputLine("interval: %u", otChannelMonitorGetSampleInterval(GetInstancePtr()));
OutputLine("threshold: %d", otChannelMonitorGetRssiThreshold(GetInstancePtr()));
OutputLine("window: %u", otChannelMonitorGetSampleWindow(GetInstancePtr()));
OutputLine("count: %u", otChannelMonitorGetSampleCount(GetInstancePtr()));
OutputLine("occupancies:");
for (uint8_t channel = 0; channel < channelNum; channel++)
{
uint32_t occupancy = 0;
if (!((1UL << channel) & channelMask))
{
continue;
}
occupancy = otChannelMonitorGetChannelOccupancy(GetInstancePtr(), channel);
OutputFormat("ch %d (0x%04x) ", channel, occupancy);
occupancy = (occupancy * 10000) / 0xffff;
OutputLine("%2d.%02d%% busy", occupancy / 100, occupancy % 100);
}
OutputLine("");
}
}
/**
* @cli channel monitor start
* @code
* channel monitor start
* channel monitor start
* Done
* @endcode
* @par
* Start the channel monitor.
* OT CLI sends a boolean value of `true` to #otChannelMonitorSetEnabled.
* `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` is required.
* @sa otChannelMonitorSetEnabled
*/
else if (aArgs[1] == "start")
{
error = otChannelMonitorSetEnabled(GetInstancePtr(), true);
}
/**
* @cli channel monitor stop
* @code
* channel monitor stop
* channel monitor stop
* Done
* @endcode
* @par
* Stop the channel monitor.
* OT CLI sends a boolean value of `false` to #otChannelMonitorSetEnabled.
* `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` is required.
* @sa otChannelMonitorSetEnabled
*/
else if (aArgs[1] == "stop")
{
error = otChannelMonitorSetEnabled(GetInstancePtr(), false);
}
else
{
ExitNow(error = OT_ERROR_INVALID_ARGS);
}
}
#endif // OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE
#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && OPENTHREAD_FTD
else if (aArgs[0] == "manager")
{
/**
* @cli channel manager
* @code
* channel manager
* channel: 11
* auto: 1
* delay: 120
* interval: 10800
* supported: { 11-26}
* favored: { 11-26}
* Done
* @endcode
* @par
* Get the channel manager state.
* `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` is required.
* @sa otChannelManagerGetRequestedChannel
*/
if (aArgs[1].IsEmpty())
{
OutputLine("channel: %d", otChannelManagerGetRequestedChannel(GetInstancePtr()));
OutputLine("auto: %d", otChannelManagerGetAutoChannelSelectionEnabled(GetInstancePtr()));
if (otChannelManagerGetAutoChannelSelectionEnabled(GetInstancePtr()))
{
Mac::ChannelMask supportedMask(otChannelManagerGetSupportedChannels(GetInstancePtr()));
Mac::ChannelMask favoredMask(otChannelManagerGetFavoredChannels(GetInstancePtr()));
OutputLine("delay: %d", otChannelManagerGetDelay(GetInstancePtr()));
OutputLine("interval: %u", otChannelManagerGetAutoChannelSelectionInterval(GetInstancePtr()));
OutputLine("cca threshold: 0x%04x", otChannelManagerGetCcaFailureRateThreshold(GetInstancePtr()));
OutputLine("supported: %s", supportedMask.ToString().AsCString());
OutputLine("favored: %s", supportedMask.ToString().AsCString());
}
}
/**
* @cli channel manager change
* @code
* channel manager change 11
* channel manager change 11
* Done
* @endcode
* @cparam channel manager change @ca{channel-num}
* @par
* `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` is required.
* @par api_copy
* #otChannelManagerRequestChannelChange
*/
else if (aArgs[1] == "change")
{
error = ProcessSet(aArgs + 2, otChannelManagerRequestChannelChange);
}
#if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE
/**
* @cli channel manager select
* @code
* channel manager select 1
* channel manager select 1
* Done
* @endcode
* @cparam channel manager select @ca{skip-quality-check}
* Use a `1` or `0` for the boolean `skip-quality-check`.
* @par
* `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` and `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` are required.
* @par api_copy
* #otChannelManagerRequestChannelSelect
*/
else if (aArgs[1] == "select")
{
bool enable;
SuccessOrExit(error = aArgs[2].ParseAsBool(enable));
error = otChannelManagerRequestChannelSelect(GetInstancePtr(), enable);
}
#endif
/**
* @cli channel manager auto
* @code
* channel manager auto 1
* channel manager auto 1
* Done
* @endcode
* @cparam channel manager auto @ca{enable}
* `1` is a boolean to `enable`.
* @par
* `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` and `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` are required.
* @par api_copy
* #otChannelManagerSetAutoChannelSelectionEnabled
*/
else if (aArgs[1] == "auto")
{
bool enable;
SuccessOrExit(error = aArgs[2].ParseAsBool(enable));
otChannelManagerSetAutoChannelSelectionEnabled(GetInstancePtr(), enable);
}
/**
* @cli channel manager delay
* @code
* channel manager delay 120
* channel manager delay 120
* Done
* @endcode
* @cparam channel manager delay @ca{delay-seconds}
* @par
* `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` and `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` are required.
* @par api_copy
* #otChannelManagerSetDelay
*/
else if (aArgs[1] == "delay")
{
error = ProcessSet(aArgs + 2, otChannelManagerSetDelay);
}
/**
* @cli channel manager interval
* @code
* channel manager interval 10800
* channel manager interval 10800
* Done
* @endcode
* @cparam channel manager interval @ca{interval-seconds}
* @par
* `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` and `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` are required.
* @par api_copy
* #otChannelManagerSetAutoChannelSelectionInterval
*/
else if (aArgs[1] == "interval")
{
error = ProcessSet(aArgs + 2, otChannelManagerSetAutoChannelSelectionInterval);
}
/**
* @cli channel manager supported
* @code
* channel manager supported 0x7fffc00
* channel manager supported 0x7fffc00
* Done
* @endcode
* @cparam channel manager supported @ca{mask}
* @par
* `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` and `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` are required.
* @par api_copy
* #otChannelManagerSetSupportedChannels
*/
else if (aArgs[1] == "supported")
{
error = ProcessSet(aArgs + 2, otChannelManagerSetSupportedChannels);
}
/**
* @cli channel manager favored
* @code
* channel manager favored 0x7fffc00
* channel manager favored 0x7fffc00
* Done
* @endcode
* @cparam channel manager favored @ca{mask}
* @par
* `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` and `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` are required.
* @par api_copy
* #otChannelManagerSetFavoredChannels
*/
else if (aArgs[1] == "favored")
{
error = ProcessSet(aArgs + 2, otChannelManagerSetFavoredChannels);
}
/**
* @cli channel manager threshold
* @code
* channel manager threshold 0xffff
* channel manager threshold 0xffff
* Done
* @endcode
* @cparam channel manager threshold @ca{threshold-percent}
* Use a hex value for `threshold-percent`. `0` maps to 0% and `0xffff` maps to 100%.
* @par
* `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` and `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` are required.
* @par api_copy
* #otChannelManagerSetCcaFailureRateThreshold
*/
else if (aArgs[1] == "threshold")
{
error = ProcessSet(aArgs + 2, otChannelManagerSetCcaFailureRateThreshold);
}
else
{
ExitNow(error = OT_ERROR_INVALID_ARGS);
}
}
#endif // OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && OPENTHREAD_FTD
else
{
ExitNow(error = ProcessGetSet(aArgs, otLinkGetChannel, otLinkSetChannel));
}
exit:
return error;
}
#if OPENTHREAD_FTD
template <> otError Interpreter::Process<Cmd("child")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
otChildInfo childInfo;
uint16_t childId;
bool isTable;
otLinkModeConfig linkMode;
char linkModeString[kLinkModeStringSize];
isTable = (aArgs[0] == "table");
if (isTable || (aArgs[0] == "list"))
{
uint16_t maxChildren;
if (isTable)
{
static const char *const kChildTableTitles[] = {
"ID", "RLOC16", "Timeout", "Age", "LQ In", "C_VN", "R",
"D", "N", "Ver", "CSL", "QMsgCnt", "Extended MAC",
};
static const uint8_t kChildTableColumnWidths[] = {
5, 8, 12, 12, 7, 6, 1, 1, 1, 3, 3, 7, 18,
};
OutputTableHeader(kChildTableTitles, kChildTableColumnWidths);
}
maxChildren = otThreadGetMaxAllowedChildren(GetInstancePtr());
for (uint16_t i = 0; i < maxChildren; i++)
{
if ((otThreadGetChildInfoByIndex(GetInstancePtr(), i, &childInfo) != OT_ERROR_NONE) ||
childInfo.mIsStateRestoring)
{
continue;
}
if (isTable)
{
OutputFormat("| %3d ", childInfo.mChildId);
OutputFormat("| 0x%04x ", childInfo.mRloc16);
OutputFormat("| %10d ", childInfo.mTimeout);
OutputFormat("| %10d ", childInfo.mAge);
OutputFormat("| %5d ", childInfo.mLinkQualityIn);
OutputFormat("| %4d ", childInfo.mNetworkDataVersion);
OutputFormat("|%1d", childInfo.mRxOnWhenIdle);
OutputFormat("|%1d", childInfo.mFullThreadDevice);
OutputFormat("|%1d", childInfo.mFullNetworkData);
OutputFormat("|%3d", childInfo.mVersion);
OutputFormat("| %1d ", childInfo.mIsCslSynced);
OutputFormat("| %5d ", childInfo.mQueuedMessageCnt);
OutputFormat("| ");
OutputExtAddress(childInfo.mExtAddress);
OutputLine(" |");
}
else
{
OutputFormat("%d ", childInfo.mChildId);
}
}
OutputLine("");
ExitNow();
}
SuccessOrExit(error = aArgs[0].ParseAsUint16(childId));
SuccessOrExit(error = otThreadGetChildInfoById(GetInstancePtr(), childId, &childInfo));
OutputLine("Child ID: %d", childInfo.mChildId);
OutputLine("Rloc: %04x", childInfo.mRloc16);
OutputFormat("Ext Addr: ");
OutputExtAddressLine(childInfo.mExtAddress);
linkMode.mRxOnWhenIdle = childInfo.mRxOnWhenIdle;
linkMode.mDeviceType = childInfo.mFullThreadDevice;
linkMode.mNetworkData = childInfo.mFullThreadDevice;
OutputLine("Mode: %s", LinkModeToString(linkMode, linkModeString));
OutputLine("Net Data: %d", childInfo.mNetworkDataVersion);
OutputLine("Timeout: %d", childInfo.mTimeout);
OutputLine("Age: %d", childInfo.mAge);
OutputLine("Link Quality In: %d", childInfo.mLinkQualityIn);
OutputLine("RSSI: %d", childInfo.mAverageRssi);
exit:
return error;
}
template <> otError Interpreter::Process<Cmd("childip")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
if (aArgs[0].IsEmpty())
{
uint16_t maxChildren = otThreadGetMaxAllowedChildren(GetInstancePtr());
for (uint16_t childIndex = 0; childIndex < maxChildren; childIndex++)
{
otChildIp6AddressIterator iterator = OT_CHILD_IP6_ADDRESS_ITERATOR_INIT;
otIp6Address ip6Address;
otChildInfo childInfo;
if ((otThreadGetChildInfoByIndex(GetInstancePtr(), childIndex, &childInfo) != OT_ERROR_NONE) ||
childInfo.mIsStateRestoring)
{
continue;
}
iterator = OT_CHILD_IP6_ADDRESS_ITERATOR_INIT;
while (otThreadGetChildNextIp6Address(GetInstancePtr(), childIndex, &iterator, &ip6Address) ==
OT_ERROR_NONE)
{
OutputFormat("%04x: ", childInfo.mRloc16);
OutputIp6AddressLine(ip6Address);
}
}
}
else if (aArgs[0] == "max")
{
#if !OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
error = ProcessGet(aArgs + 1, otThreadGetMaxChildIpAddresses);
#else
error = ProcessGetSet(aArgs + 1, otThreadGetMaxChildIpAddresses, otThreadSetMaxChildIpAddresses);
#endif
}
else
{
error = OT_ERROR_INVALID_COMMAND;
}
return error;
}
template <> otError Interpreter::Process<Cmd("childmax")>(Arg aArgs[])
{
return ProcessGetSet(aArgs, otThreadGetMaxAllowedChildren, otThreadSetMaxAllowedChildren);
}
#endif // OPENTHREAD_FTD
#if OPENTHREAD_CONFIG_CHILD_SUPERVISION_ENABLE
template <> otError Interpreter::Process<Cmd("childsupervision")>(Arg aArgs[])
{
otError error = OT_ERROR_INVALID_ARGS;
if (aArgs[0] == "checktimeout")
{
error = ProcessGetSet(aArgs + 1, otChildSupervisionGetCheckTimeout, otChildSupervisionSetCheckTimeout);
}
#if OPENTHREAD_FTD
else if (aArgs[0] == "interval")
{
error = ProcessGetSet(aArgs + 1, otChildSupervisionGetInterval, otChildSupervisionSetInterval);
}
#endif
return error;
}
#endif // OPENTHREAD_CONFIG_CHILD_SUPERVISION_ENABLE
template <> otError Interpreter::Process<Cmd("childtimeout")>(Arg aArgs[])
{
return ProcessGetSet(aArgs, otThreadGetChildTimeout, otThreadSetChildTimeout);
}
#if OPENTHREAD_CONFIG_COAP_API_ENABLE
template <> otError Interpreter::Process<Cmd("coap")>(Arg aArgs[])
{
return mCoap.Process(aArgs);
}
#endif
#if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE
template <> otError Interpreter::Process<Cmd("coaps")>(Arg aArgs[])
{
return mCoapSecure.Process(aArgs);
}
#endif
#if OPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE
template <> otError Interpreter::Process<Cmd("coex")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
bool enable;
if (aArgs[0].IsEmpty())
{
OutputEnabledDisabledStatus(otPlatRadioIsCoexEnabled(GetInstancePtr()));
}
else if (ParseEnableOrDisable(aArgs[0], enable) == OT_ERROR_NONE)
{
error = otPlatRadioSetCoexEnabled(GetInstancePtr(), enable);
}
else if (aArgs[0] == "metrics")
{
struct RadioCoexMetricName
{
const uint32_t otRadioCoexMetrics::*mValuePtr;
const char * mName;
};
static const RadioCoexMetricName kTxMetricNames[] = {
{&otRadioCoexMetrics::mNumTxRequest, "Request"},
{&otRadioCoexMetrics::mNumTxGrantImmediate, "Grant Immediate"},
{&otRadioCoexMetrics::mNumTxGrantWait, "Grant Wait"},
{&otRadioCoexMetrics::mNumTxGrantWaitActivated, "Grant Wait Activated"},
{&otRadioCoexMetrics::mNumTxGrantWaitTimeout, "Grant Wait Timeout"},
{&otRadioCoexMetrics::mNumTxGrantDeactivatedDuringRequest, "Grant Deactivated During Request"},
{&otRadioCoexMetrics::mNumTxDelayedGrant, "Delayed Grant"},
{&otRadioCoexMetrics::mAvgTxRequestToGrantTime, "Average Request To Grant Time"},
};
static const RadioCoexMetricName kRxMetricNames[] = {
{&otRadioCoexMetrics::mNumRxRequest, "Request"},
{&otRadioCoexMetrics::mNumRxGrantImmediate, "Grant Immediate"},
{&otRadioCoexMetrics::mNumRxGrantWait, "Grant Wait"},
{&otRadioCoexMetrics::mNumRxGrantWaitActivated, "Grant Wait Activated"},
{&otRadioCoexMetrics::mNumRxGrantWaitTimeout, "Grant Wait Timeout"},
{&otRadioCoexMetrics::mNumRxGrantDeactivatedDuringRequest, "Grant Deactivated During Request"},
{&otRadioCoexMetrics::mNumRxDelayedGrant, "Delayed Grant"},
{&otRadioCoexMetrics::mAvgRxRequestToGrantTime, "Average Request To Grant Time"},
{&otRadioCoexMetrics::mNumRxGrantNone, "Grant None"},
};
otRadioCoexMetrics metrics;
SuccessOrExit(error = otPlatRadioGetCoexMetrics(GetInstancePtr(), &metrics));
OutputLine("Stopped: %s", metrics.mStopped ? "true" : "false");
OutputLine("Grant Glitch: %u", metrics.mNumGrantGlitch);
OutputLine("Transmit metrics");
for (const RadioCoexMetricName &metric : kTxMetricNames)
{
OutputLine(kIndentSize, "%s: %u", metric.mName, metrics.*metric.mValuePtr);
}
OutputLine("Receive metrics");
for (const RadioCoexMetricName &metric : kRxMetricNames)
{
OutputLine(kIndentSize, "%s: %u", metric.mName, metrics.*metric.mValuePtr);
}
}
else
{
error = OT_ERROR_INVALID_ARGS;
}
exit:
return error;
}
#endif // OPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE
#if OPENTHREAD_FTD
/**
* @cli contextreusedelay (get,set)
* @code
* contextreusedelay
* 11
* Done
* @endcode
* @code
* contextreusedelay 11
* Done
* @endcode
* @cparam contextreusedelay @ca{delay}
* Use the optional `delay` argument to set the `CONTEXT_ID_REUSE_DELAY`.
* @par
* Gets or sets the `CONTEXT_ID_REUSE_DELAY` value.
* @sa otThreadGetContextIdReuseDelay
* @sa otThreadSetContextIdReuseDelay
*/
template <> otError Interpreter::Process<Cmd("contextreusedelay")>(Arg aArgs[])
{
return ProcessGetSet(aArgs, otThreadGetContextIdReuseDelay, otThreadSetContextIdReuseDelay);
}
#endif
template <> otError Interpreter::Process<Cmd("counters")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
/**
* @cli counters
* @code
* counters
* ip
* mac
* mle
* Done
* @endcode
* @par
* Gets the supported counter names.
*/
if (aArgs[0].IsEmpty())
{
OutputLine("ip");
OutputLine("mac");
OutputLine("mle");
}
/**
* @cli counters (mac)
* @code
* counters mac
* TxTotal: 10
* TxUnicast: 3
* TxBroadcast: 7
* TxAckRequested: 3
* TxAcked: 3
* TxNoAckRequested: 7
* TxData: 10
* TxDataPoll: 0
* TxBeacon: 0
* TxBeaconRequest: 0
* TxOther: 0
* TxRetry: 0
* TxErrCca: 0
* TxErrBusyChannel: 0
* RxTotal: 2
* RxUnicast: 1
* RxBroadcast: 1
* RxData: 2
* RxDataPoll: 0
* RxBeacon: 0
* RxBeaconRequest: 0
* RxOther: 0
* RxAddressFiltered: 0
* RxDestAddrFiltered: 0
* RxDuplicated: 0
* RxErrNoFrame: 0
* RxErrNoUnknownNeighbor: 0
* RxErrInvalidSrcAddr: 0
* RxErrSec: 0
* RxErrFcs: 0
* RxErrOther: 0
* Done
* @endcode
* @cparam counters @ca{mac}
* @par api_copy
* #otLinkGetCounters
*/
else if (aArgs[0] == "mac")
{
if (aArgs[1].IsEmpty())
{
struct MacCounterName
{
const uint32_t otMacCounters::*mValuePtr;
const char * mName;
};
static const MacCounterName kTxCounterNames[] = {
{&otMacCounters::mTxUnicast, "TxUnicast"},
{&otMacCounters::mTxBroadcast, "TxBroadcast"},
{&otMacCounters::mTxAckRequested, "TxAckRequested"},
{&otMacCounters::mTxAcked, "TxAcked"},
{&otMacCounters::mTxNoAckRequested, "TxNoAckRequested"},
{&otMacCounters::mTxData, "TxData"},
{&otMacCounters::mTxDataPoll, "TxDataPoll"},
{&otMacCounters::mTxBeacon, "TxBeacon"},
{&otMacCounters::mTxBeaconRequest, "TxBeaconRequest"},
{&otMacCounters::mTxOther, "TxOther"},
{&otMacCounters::mTxRetry, "TxRetry"},
{&otMacCounters::mTxErrCca, "TxErrCca"},
{&otMacCounters::mTxErrBusyChannel, "TxErrBusyChannel"},
};
static const MacCounterName kRxCounterNames[] = {
{&otMacCounters::mRxUnicast, "RxUnicast"},
{&otMacCounters::mRxBroadcast, "RxBroadcast"},
{&otMacCounters::mRxData, "RxData"},
{&otMacCounters::mRxDataPoll, "RxDataPoll"},
{&otMacCounters::mRxBeacon, "RxBeacon"},
{&otMacCounters::mRxBeaconRequest, "RxBeaconRequest"},
{&otMacCounters::mRxOther, "RxOther"},
{&otMacCounters::mRxAddressFiltered, "RxAddressFiltered"},
{&otMacCounters::mRxDestAddrFiltered, "RxDestAddrFiltered"},
{&otMacCounters::mRxDuplicated, "RxDuplicated"},
{&otMacCounters::mRxErrNoFrame, "RxErrNoFrame"},
{&otMacCounters::mRxErrUnknownNeighbor, "RxErrNoUnknownNeighbor"},
{&otMacCounters::mRxErrInvalidSrcAddr, "RxErrInvalidSrcAddr"},
{&otMacCounters::mRxErrSec, "RxErrSec"},
{&otMacCounters::mRxErrFcs, "RxErrFcs"},
{&otMacCounters::mRxErrOther, "RxErrOther"},
};
const otMacCounters *macCounters = otLinkGetCounters(GetInstancePtr());
OutputLine("TxTotal: %d", macCounters->mTxTotal);
for (const MacCounterName &counter : kTxCounterNames)
{
OutputLine(kIndentSize, "%s: %u", counter.mName, macCounters->*counter.mValuePtr);
}
OutputLine("RxTotal: %d", macCounters->mRxTotal);
for (const MacCounterName &counter : kRxCounterNames)
{
OutputLine(kIndentSize, "%s: %u", counter.mName, macCounters->*counter.mValuePtr);
}
}
/**
* @cli counters mac reset
* @code
* counters mac reset
* Done
* @endcode
* @cparam counters @ca{mac} reset
* @par api_copy
* #otLinkResetCounters
*/
else if ((aArgs[1] == "reset") && aArgs[2].IsEmpty())
{
otLinkResetCounters(GetInstancePtr());
}
else
{
error = OT_ERROR_INVALID_ARGS;
}
}
/**
* @cli counters (mle)
* @code
* counters mle
* Role Disabled: 0
* Role Detached: 1
* Role Child: 0
* Role Router: 0
* Role Leader: 1
* Attach Attempts: 1
* Partition Id Changes: 1
* Better Partition Attach Attempts: 0
* Parent Changes: 0
* Done
* @endcode
* @cparam counters @ca{mle}
* @par api_copy
* #otThreadGetMleCounters
*/
else if (aArgs[0] == "mle")
{
if (aArgs[1].IsEmpty())
{
struct MleCounterName
{
const uint16_t otMleCounters::*mValuePtr;
const char * mName;
};
static const MleCounterName kCounterNames[] = {
{&otMleCounters::mDisabledRole, "Role Disabled"},
{&otMleCounters::mDetachedRole, "Role Detached"},
{&otMleCounters::mChildRole, "Role Child"},
{&otMleCounters::mRouterRole, "Role Router"},
{&otMleCounters::mLeaderRole, "Role Leader"},
{&otMleCounters::mAttachAttempts, "Attach Attempts"},
{&otMleCounters::mPartitionIdChanges, "Partition Id Changes"},
{&otMleCounters::mBetterPartitionAttachAttempts, "Better Partition Attach Attempts"},
{&otMleCounters::mParentChanges, "Parent Changes"},
};
const otMleCounters *mleCounters = otThreadGetMleCounters(GetInstancePtr());
for (const MleCounterName &counter : kCounterNames)
{
OutputLine("%s: %d", counter.mName, mleCounters->*counter.mValuePtr);
}
}
/**
* @cli counters mle reset
* @code
* counters mle reset
* Done
* @endcode
* @cparam counters @ca{mle} reset
* @par api_copy
* #otThreadResetMleCounters
*/
else if ((aArgs[1] == "reset") && aArgs[2].IsEmpty())
{
otThreadResetMleCounters(GetInstancePtr());
}
else
{
error = OT_ERROR_INVALID_ARGS;
}
}
/**
* @cli counters ip
* @code
* counters ip
* TxSuccess: 10
* TxFailed: 0
* RxSuccess: 5
* RxFailed: 0
* Done
* @endcode
* @cparam counters @ca{ip}
* @par api_copy
* #otThreadGetIp6Counters
*/
else if (aArgs[0] == "ip")
{
if (aArgs[1].IsEmpty())
{
struct IpCounterName
{
const uint32_t otIpCounters::*mValuePtr;
const char * mName;
};
static const IpCounterName kCounterNames[] = {
{&otIpCounters::mTxSuccess, "TxSuccess"},
{&otIpCounters::mTxFailure, "TxFailed"},
{&otIpCounters::mRxSuccess, "RxSuccess"},
{&otIpCounters::mRxFailure, "RxFailed"},
};
const otIpCounters *ipCounters = otThreadGetIp6Counters(GetInstancePtr());
for (const IpCounterName &counter : kCounterNames)
{
OutputLine("%s: %d", counter.mName, ipCounters->*counter.mValuePtr);
}
}
/**
* @cli counters ip reset
* @code
* counters ip reset
* Done
* @endcode
* @cparam counters @ca{ip} reset
* @par api_copy
* #otThreadResetIp6Counters
*/
else if ((aArgs[1] == "reset") && aArgs[2].IsEmpty())
{
otThreadResetIp6Counters(GetInstancePtr());
}
else
{
error = OT_ERROR_INVALID_ARGS;
}
}
else
{
error = OT_ERROR_INVALID_ARGS;
}
return error;
}
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
template <> otError Interpreter::Process<Cmd("csl")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
/**
* @cli csl
* @code
* csl
* Channel: 11
* Period: 1000 (in units of 10 symbols), 160ms
* Timeout: 1000s
* Done
* @endcode
* @par
* Gets the CSL configuration.
* @sa otLinkCslGetChannel
* @sa otLinkCslGetPeriod
* @sa otLinkCslGetPeriod
* @sa otLinkCslGetTimeout
*/
if (aArgs[0].IsEmpty())
{
OutputLine("Channel: %u", otLinkCslGetChannel(GetInstancePtr()));
OutputLine("Period: %u(in units of 10 symbols), %ums", otLinkCslGetPeriod(GetInstancePtr()),
otLinkCslGetPeriod(GetInstancePtr()) * kUsPerTenSymbols / 1000);
OutputLine("Timeout: %us", otLinkCslGetTimeout(GetInstancePtr()));
}
/**
* @cli csl channel
* @code
* csl channel 20
* Done
* @endcode
* @cparam csl channel @ca{channel}
* @par api_copy
* #otLinkCslSetChannel
*/
else if (aArgs[0] == "channel")
{
error = ProcessSet(aArgs + 1, otLinkCslSetChannel);
}
/**
* @cli csl period
* @code
* csl period 3000
* Done
* @endcode
* @cparam csl period @ca{period}
* @par api_copy
* #otLinkCslSetPeriod
*/
else if (aArgs[0] == "period")
{
error = ProcessSet(aArgs + 1, otLinkCslSetPeriod);
}
/**
* @cli csl timeout
* @code
* cls timeout 10
* Done
* @endcode
* @cparam csl timeout @ca{timeout}
* @par api_copy
* #otLinkCslSetTimeout
*/
else if (aArgs[0] == "timeout")
{
error = ProcessSet(aArgs + 1, otLinkCslSetTimeout);
}
else
{
error = OT_ERROR_INVALID_ARGS;
}
return error;
}
#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
#if OPENTHREAD_FTD
template <> otError Interpreter::Process<Cmd("delaytimermin")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
if (aArgs[0].IsEmpty())
{
OutputLine("%d", (otDatasetGetDelayTimerMinimal(GetInstancePtr()) / 1000));
}
else if (aArgs[1].IsEmpty())
{
uint32_t delay;
SuccessOrExit(error = aArgs[0].ParseAsUint32(delay));
SuccessOrExit(error = otDatasetSetDelayTimerMinimal(GetInstancePtr(), static_cast<uint32_t>(delay * 1000)));
}
else
{
error = OT_ERROR_INVALID_ARGS;
}
exit:
return error;
}
#endif
template <> otError Interpreter::Process<Cmd("detach")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
if (aArgs[0] == "async")
{
SuccessOrExit(error = otThreadDetachGracefully(GetInstancePtr(), nullptr, nullptr));
}
else
{
SuccessOrExit(error =
otThreadDetachGracefully(GetInstancePtr(), &Interpreter::HandleDetachGracefullyResult, this));
error = OT_ERROR_PENDING;
}
exit:
return error;
}
template <> otError Interpreter::Process<Cmd("discover")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
uint32_t scanChannels = 0;
if (!aArgs[0].IsEmpty())
{
uint8_t channel;
SuccessOrExit(error = aArgs[0].ParseAsUint8(channel));
VerifyOrExit(channel < sizeof(scanChannels) * CHAR_BIT, error = OT_ERROR_INVALID_ARGS);
scanChannels = 1 << channel;
}
SuccessOrExit(error = otThreadDiscover(GetInstancePtr(), scanChannels, OT_PANID_BROADCAST, false, false,
&Interpreter::HandleActiveScanResult, this));
static const char *const kScanTableTitles[] = {
"Network Name", "Extended PAN", "PAN", "MAC Address", "Ch", "dBm", "LQI",
};
static const uint8_t kScanTableColumnWidths[] = {
18, 18, 6, 18, 4, 5, 5,
};
OutputTableHeader(kScanTableTitles, kScanTableColumnWidths);
error = OT_ERROR_PENDING;
exit:
return error;
}
template <> otError Interpreter::Process<Cmd("dns")>(Arg aArgs[])
{
OT_UNUSED_VARIABLE(aArgs);
otError error = OT_ERROR_NONE;
#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
otDnsQueryConfig queryConfig;
otDnsQueryConfig *config = &queryConfig;
#endif
if (aArgs[0].IsEmpty())
{
error = OT_ERROR_INVALID_ARGS;
}
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
else if (aArgs[0] == "compression")
{
if (aArgs[1].IsEmpty())
{
OutputEnabledDisabledStatus(otDnsIsNameCompressionEnabled());
}
else
{
bool enable;
SuccessOrExit(error = ParseEnableOrDisable(aArgs[1], enable));
otDnsSetNameCompressionEnabled(enable);
}
}
#endif // OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
else if (aArgs[0] == "config")
{
if (aArgs[1].IsEmpty())
{
const otDnsQueryConfig *defaultConfig = otDnsClientGetDefaultConfig(GetInstancePtr());
OutputFormat("Server: ");
OutputSockAddrLine(defaultConfig->mServerSockAddr);
OutputLine("ResponseTimeout: %u ms", defaultConfig->mResponseTimeout);
OutputLine("MaxTxAttempts: %u", defaultConfig->mMaxTxAttempts);
OutputLine("RecursionDesired: %s",
(defaultConfig->mRecursionFlag == OT_DNS_FLAG_RECURSION_DESIRED) ? "yes" : "no");
}
else
{
SuccessOrExit(error = GetDnsConfig(aArgs + 1, config));
otDnsClientSetDefaultConfig(GetInstancePtr(), config);
}
}
else if (aArgs[0] == "resolve")
{
VerifyOrExit(!aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
SuccessOrExit(error = GetDnsConfig(aArgs + 2, config));
SuccessOrExit(error = otDnsClientResolveAddress(GetInstancePtr(), aArgs[1].GetCString(),
&Interpreter::HandleDnsAddressResponse, this, config));
error = OT_ERROR_PENDING;
}
#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE
else if (aArgs[0] == "resolve4")
{
VerifyOrExit(!aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
SuccessOrExit(error = GetDnsConfig(aArgs + 2, config));
SuccessOrExit(error = otDnsClientResolveIp4Address(GetInstancePtr(), aArgs[1].GetCString(),
&Interpreter::HandleDnsAddressResponse, this, config));
error = OT_ERROR_PENDING;
}
#endif
#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
else if (aArgs[0] == "browse")
{
VerifyOrExit(!aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
SuccessOrExit(error = GetDnsConfig(aArgs + 2, config));
SuccessOrExit(error = otDnsClientBrowse(GetInstancePtr(), aArgs[1].GetCString(),
&Interpreter::HandleDnsBrowseResponse, this, config));
error = OT_ERROR_PENDING;
}
else if (aArgs[0] == "service")
{
VerifyOrExit(!aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
SuccessOrExit(error = GetDnsConfig(aArgs + 3, config));
SuccessOrExit(error = otDnsClientResolveService(GetInstancePtr(), aArgs[1].GetCString(), aArgs[2].GetCString(),
&Interpreter::HandleDnsServiceResponse, this, config));
error = OT_ERROR_PENDING;
}
#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
#endif // OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
else
{
ExitNow(error = OT_ERROR_INVALID_COMMAND);
}
exit:
return error;
}
#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
otError Interpreter::GetDnsConfig(Arg aArgs[], otDnsQueryConfig *&aConfig)
{
// This method gets the optional DNS config from `aArgs[]`.
// The format: `[server IPv6 address] [server port] [timeout]
// [max tx attempt] [recursion desired]`.
otError error = OT_ERROR_NONE;
bool recursionDesired;
memset(aConfig, 0, sizeof(otDnsQueryConfig));
VerifyOrExit(!aArgs[0].IsEmpty(), aConfig = nullptr);
SuccessOrExit(error = aArgs[0].ParseAsIp6Address(aConfig->mServerSockAddr.mAddress));
VerifyOrExit(!aArgs[1].IsEmpty());
SuccessOrExit(error = aArgs[1].ParseAsUint16(aConfig->mServerSockAddr.mPort));
VerifyOrExit(!aArgs[2].IsEmpty());
SuccessOrExit(error = aArgs[2].ParseAsUint32(aConfig->mResponseTimeout));
VerifyOrExit(!aArgs[3].IsEmpty());
SuccessOrExit(error = aArgs[3].ParseAsUint8(aConfig->mMaxTxAttempts));
VerifyOrExit(!aArgs[4].IsEmpty());
SuccessOrExit(error = aArgs[4].ParseAsBool(recursionDesired));
aConfig->mRecursionFlag = recursionDesired ? OT_DNS_FLAG_RECURSION_DESIRED : OT_DNS_FLAG_NO_RECURSION;
exit:
return error;
}
void Interpreter::HandleDnsAddressResponse(otError aError, const otDnsAddressResponse *aResponse, void *aContext)
{
static_cast<Interpreter *>(aContext)->HandleDnsAddressResponse(aError, aResponse);
}
void Interpreter::HandleDnsAddressResponse(otError aError, const otDnsAddressResponse *aResponse)
{
char hostName[OT_DNS_MAX_NAME_SIZE];
otIp6Address address;
uint32_t ttl;
IgnoreError(otDnsAddressResponseGetHostName(aResponse, hostName, sizeof(hostName)));
OutputFormat("DNS response for %s - ", hostName);
if (aError == OT_ERROR_NONE)
{
uint16_t index = 0;
while (otDnsAddressResponseGetAddress(aResponse, index, &address, &ttl) == OT_ERROR_NONE)
{
OutputIp6Address(address);
OutputFormat(" TTL:%u ", ttl);
index++;
}
}
OutputLine("");
OutputResult(aError);
}
#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
void Interpreter::OutputDnsServiceInfo(uint8_t aIndentSize, const otDnsServiceInfo &aServiceInfo)
{
OutputLine(aIndentSize, "Port:%d, Priority:%d, Weight:%d, TTL:%u", aServiceInfo.mPort, aServiceInfo.mPriority,
aServiceInfo.mWeight, aServiceInfo.mTtl);
OutputLine(aIndentSize, "Host:%s", aServiceInfo.mHostNameBuffer);
OutputFormat(aIndentSize, "HostAddress:");
OutputIp6Address(aServiceInfo.mHostAddress);
OutputLine(" TTL:%u", aServiceInfo.mHostAddressTtl);
OutputFormat(aIndentSize, "TXT:");
OutputDnsTxtData(aServiceInfo.mTxtData, aServiceInfo.mTxtDataSize);
OutputLine(" TTL:%u", aServiceInfo.mTxtDataTtl);
}
void Interpreter::HandleDnsBrowseResponse(otError aError, const otDnsBrowseResponse *aResponse, void *aContext)
{
static_cast<Interpreter *>(aContext)->HandleDnsBrowseResponse(aError, aResponse);
}
void Interpreter::HandleDnsBrowseResponse(otError aError, const otDnsBrowseResponse *aResponse)
{
char name[OT_DNS_MAX_NAME_SIZE];
char label[OT_DNS_MAX_LABEL_SIZE];
uint8_t txtBuffer[255];
otDnsServiceInfo serviceInfo;
IgnoreError(otDnsBrowseResponseGetServiceName(aResponse, name, sizeof(name)));
OutputLine("DNS browse response for %s", name);
if (aError == OT_ERROR_NONE)
{
uint16_t index = 0;
while (otDnsBrowseResponseGetServiceInstance(aResponse, index, label, sizeof(label)) == OT_ERROR_NONE)
{
OutputLine("%s", label);
index++;
serviceInfo.mHostNameBuffer = name;
serviceInfo.mHostNameBufferSize = sizeof(name);
serviceInfo.mTxtData = txtBuffer;
serviceInfo.mTxtDataSize = sizeof(txtBuffer);
if (otDnsBrowseResponseGetServiceInfo(aResponse, label, &serviceInfo) == OT_ERROR_NONE)
{
OutputDnsServiceInfo(kIndentSize, serviceInfo);
}
OutputLine("");
}
}
OutputResult(aError);
}
void Interpreter::HandleDnsServiceResponse(otError aError, const otDnsServiceResponse *aResponse, void *aContext)
{
static_cast<Interpreter *>(aContext)->HandleDnsServiceResponse(aError, aResponse);
}
void Interpreter::HandleDnsServiceResponse(otError aError, const otDnsServiceResponse *aResponse)
{
char name[OT_DNS_MAX_NAME_SIZE];
char label[OT_DNS_MAX_LABEL_SIZE];
uint8_t txtBuffer[255];
otDnsServiceInfo serviceInfo;
IgnoreError(otDnsServiceResponseGetServiceName(aResponse, label, sizeof(label), name, sizeof(name)));
OutputLine("DNS service resolution response for %s for service %s", label, name);
if (aError == OT_ERROR_NONE)
{
serviceInfo.mHostNameBuffer = name;
serviceInfo.mHostNameBufferSize = sizeof(name);
serviceInfo.mTxtData = txtBuffer;
serviceInfo.mTxtDataSize = sizeof(txtBuffer);
if (otDnsServiceResponseGetServiceInfo(aResponse, &serviceInfo) == OT_ERROR_NONE)
{
OutputDnsServiceInfo(/* aIndetSize */ 0, serviceInfo);
OutputLine("");
}
}
OutputResult(aError);
}
#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
#endif // OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
#if OPENTHREAD_FTD
const char *EidCacheStateToString(otCacheEntryState aState)
{
static const char *const kStateStrings[4] = {
"cache",
"snoop",
"query",
"retry",
};
return Interpreter::Stringify(aState, kStateStrings);
}
void Interpreter::OutputEidCacheEntry(const otCacheEntryInfo &aEntry)
{
OutputIp6Address(aEntry.mTarget);
OutputFormat(" %04x", aEntry.mRloc16);
OutputFormat(" %s", EidCacheStateToString(aEntry.mState));
OutputFormat(" canEvict=%d", aEntry.mCanEvict);
if (aEntry.mState == OT_CACHE_ENTRY_STATE_CACHED)
{
if (aEntry.mValidLastTrans)
{
OutputFormat(" transTime=%u eid=", aEntry.mLastTransTime);
OutputIp6Address(aEntry.mMeshLocalEid);
}
}
else
{
OutputFormat(" timeout=%u", aEntry.mTimeout);
}
if (aEntry.mState == OT_CACHE_ENTRY_STATE_RETRY_QUERY)
{
OutputFormat(" retryDelay=%u", aEntry.mRetryDelay);
}
OutputLine("");
}
template <> otError Interpreter::Process<Cmd("eidcache")>(Arg aArgs[])
{
OT_UNUSED_VARIABLE(aArgs);
otCacheEntryIterator iterator;
otCacheEntryInfo entry;
memset(&iterator, 0, sizeof(iterator));
for (uint8_t i = 0;; i++)
{
SuccessOrExit(otThreadGetNextCacheEntry(GetInstancePtr(), &entry, &iterator));
OutputEidCacheEntry(entry);
}
exit:
return OT_ERROR_NONE;
}
#endif
/**
* @cli eui64
* @code
* eui64
* 0615aae900124b00
* Done
* @endcode
* @par api_copy
* #otPlatRadioGetIeeeEui64
*/
template <> otError Interpreter::Process<Cmd("eui64")>(Arg aArgs[])
{
OT_UNUSED_VARIABLE(aArgs);
otError error = OT_ERROR_NONE;
otExtAddress extAddress;
VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
otLinkGetFactoryAssignedIeeeEui64(GetInstancePtr(), &extAddress);
OutputExtAddressLine(extAddress);
exit:
return error;
}
template <> otError Interpreter::Process<Cmd("extaddr")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
if (aArgs[0].IsEmpty())
{
OutputExtAddressLine(*otLinkGetExtendedAddress(GetInstancePtr()));
}
else
{
otExtAddress extAddress;
SuccessOrExit(error = aArgs[0].ParseAsHexString(extAddress.m8));
error = otLinkSetExtendedAddress(GetInstancePtr(), &extAddress);
}
exit:
return error;
}
template <> otError Interpreter::Process<Cmd("log")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
if (aArgs[0] == "level")
{
if (aArgs[1].IsEmpty())
{
OutputLine("%d", otLoggingGetLevel());
}
else
{
#if OPENTHREAD_CONFIG_LOG_LEVEL_DYNAMIC_ENABLE
uint8_t level;
VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
SuccessOrExit(error = aArgs[1].ParseAsUint8(level));
error = otLoggingSetLevel(static_cast<otLogLevel>(level));
#else
error = OT_ERROR_INVALID_ARGS;
#endif
}
}
#if (OPENTHREAD_CONFIG_LOG_OUTPUT == OPENTHREAD_CONFIG_LOG_OUTPUT_DEBUG_UART) && OPENTHREAD_POSIX
else if (aArgs[0] == "filename")
{
VerifyOrExit(aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
SuccessOrExit(error = otPlatDebugUart_logfile(aArgs[1].GetCString()));
}
#endif
else
{
ExitNow(error = OT_ERROR_INVALID_ARGS);
}
exit:
return error;
}
template <> otError Interpreter::Process<Cmd("extpanid")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
if (aArgs[0].IsEmpty())
{
OutputBytesLine(otThreadGetExtendedPanId(GetInstancePtr())->m8);
}
else
{
otExtendedPanId extPanId;
SuccessOrExit(error = aArgs[0].ParseAsHexString(extPanId.m8));
error = otThreadSetExtendedPanId(GetInstancePtr(), &extPanId);
}
exit:
return error;
}
template <> otError Interpreter::Process<Cmd("factoryreset")>(Arg aArgs[])
{
OT_UNUSED_VARIABLE(aArgs);
otInstanceFactoryReset(GetInstancePtr());
return OT_ERROR_NONE;
}
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
template <> otError Interpreter::Process<Cmd("fake")>(Arg aArgs[])
{
otError error = OT_ERROR_INVALID_COMMAND;
if (aArgs[0] == "/a/an")
{
otIp6Address destination, target;
otIp6InterfaceIdentifier mlIid;
SuccessOrExit(error = aArgs[1].ParseAsIp6Address(destination));
SuccessOrExit(error = aArgs[2].ParseAsIp6Address(target));
SuccessOrExit(error = aArgs[3].ParseAsHexString(mlIid.mFields.m8));
otThreadSendAddressNotification(GetInstancePtr(), &destination, &target, &mlIid);
}
#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_DUA_NDPROXYING_ENABLE
else if (aArgs[0] == "/b/ba")
{
otIp6Address target;
otIp6InterfaceIdentifier mlIid;
uint32_t timeSinceLastTransaction;
SuccessOrExit(error = aArgs[1].ParseAsIp6Address(target));
SuccessOrExit(error = aArgs[2].ParseAsHexString(mlIid.mFields.m8));
SuccessOrExit(error = aArgs[3].ParseAsUint32(timeSinceLastTransaction));
error = otThreadSendProactiveBackboneNotification(GetInstancePtr(), &target, &mlIid, timeSinceLastTransaction);
}
#endif
exit:
return error;
}
#endif
template <> otError Interpreter::Process<Cmd("fem")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
if (aArgs[0].IsEmpty())
{
int8_t lnaGain;
SuccessOrExit(error = otPlatRadioGetFemLnaGain(GetInstancePtr(), &lnaGain));
OutputLine("LNA gain %d dBm", lnaGain);
}
else if (aArgs[0] == "lnagain")
{
if (aArgs[1].IsEmpty())
{
int8_t lnaGain;
SuccessOrExit(error = otPlatRadioGetFemLnaGain(GetInstancePtr(), &lnaGain));
OutputLine("%d", lnaGain);
}
else
{
int8_t lnaGain;
SuccessOrExit(error = aArgs[1].ParseAsInt8(lnaGain));
SuccessOrExit(error = otPlatRadioSetFemLnaGain(GetInstancePtr(), lnaGain));
}
}
else
{
error = OT_ERROR_INVALID_ARGS;
}
exit:
return error;
}
template <> otError Interpreter::Process<Cmd("ifconfig")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
if (aArgs[0].IsEmpty())
{
if (otIp6IsEnabled(GetInstancePtr()))
{
OutputLine("up");
}
else
{
OutputLine("down");
}
}
else if (aArgs[0] == "up")
{
SuccessOrExit(error = otIp6SetEnabled(GetInstancePtr(), true));
}
else if (aArgs[0] == "down")
{
SuccessOrExit(error = otIp6SetEnabled(GetInstancePtr(), false));
}
else
{
ExitNow(error = OT_ERROR_INVALID_ARGS);
}
exit:
return error;
}
const char *Interpreter::AddressOriginToString(uint8_t aOrigin)
{
static const char *const kOriginStrings[4] = {
"thread", // 0, OT_ADDRESS_ORIGIN_THREAD
"slaac", // 1, OT_ADDRESS_ORIGIN_SLAAC
"dhcp6", // 2, OT_ADDRESS_ORIGIN_DHCPV6
"manual", // 3, OT_ADDRESS_ORIGIN_MANUAL
};
static_assert(0 == OT_ADDRESS_ORIGIN_THREAD, "OT_ADDRESS_ORIGIN_THREAD value is incorrect");
static_assert(1 == OT_ADDRESS_ORIGIN_SLAAC, "OT_ADDRESS_ORIGIN_SLAAC value is incorrect");
static_assert(2 == OT_ADDRESS_ORIGIN_DHCPV6, "OT_ADDRESS_ORIGIN_DHCPV6 value is incorrect");
static_assert(3 == OT_ADDRESS_ORIGIN_MANUAL, "OT_ADDRESS_ORIGIN_MANUAL value is incorrect");
return Stringify(aOrigin, kOriginStrings);
}
template <> otError Interpreter::Process<Cmd("ipaddr")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
bool verbose = false;
if (aArgs[0] == "-v")
{
aArgs++;
verbose = true;
}
if (aArgs[0].IsEmpty())
{
const otNetifAddress *unicastAddrs = otIp6GetUnicastAddresses(GetInstancePtr());
for (const otNetifAddress *addr = unicastAddrs; addr; addr = addr->mNext)
{
OutputIp6Address(addr->mAddress);
if (verbose)
{
OutputFormat(" origin:%s", AddressOriginToString(addr->mAddressOrigin));
}
OutputLine("");
}
}
else if (aArgs[0] == "add")
{
otNetifAddress address;
SuccessOrExit(error = aArgs[1].ParseAsIp6Address(address.mAddress));
address.mPrefixLength = 64;
address.mPreferred = true;
address.mValid = true;
address.mAddressOrigin = OT_ADDRESS_ORIGIN_MANUAL;
error = otIp6AddUnicastAddress(GetInstancePtr(), &address);
}
else if (aArgs[0] == "del")
{
otIp6Address address;
SuccessOrExit(error = aArgs[1].ParseAsIp6Address(address));
error = otIp6RemoveUnicastAddress(GetInstancePtr(), &address);
}
else if (aArgs[0] == "linklocal")
{
OutputIp6AddressLine(*otThreadGetLinkLocalIp6Address(GetInstancePtr()));
}
else if (aArgs[0] == "rloc")
{
OutputIp6AddressLine(*otThreadGetRloc(GetInstancePtr()));
}
else if (aArgs[0] == "mleid")
{
OutputIp6AddressLine(*otThreadGetMeshLocalEid(GetInstancePtr()));
}
else
{
error = OT_ERROR_INVALID_COMMAND;
}
exit:
return error;
}
template <> otError Interpreter::Process<Cmd("ipmaddr")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
if (aArgs[0].IsEmpty())
{
for (const otNetifMulticastAddress *addr = otIp6GetMulticastAddresses(GetInstancePtr()); addr;
addr = addr->mNext)
{
OutputIp6AddressLine(addr->mAddress);
}
}
else if (aArgs[0] == "add")
{
otIp6Address address;
aArgs++;
do
{
SuccessOrExit(error = aArgs->ParseAsIp6Address(address));
SuccessOrExit(error = otIp6SubscribeMulticastAddress(GetInstancePtr(), &address));
}
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
while (!(++aArgs)->IsEmpty());
#else
while (false);
#endif
}
else if (aArgs[0] == "del")
{
otIp6Address address;
SuccessOrExit(error = aArgs[1].ParseAsIp6Address(address));
error = otIp6UnsubscribeMulticastAddress(GetInstancePtr(), &address);
}
else if (aArgs[0] == "promiscuous")
{
if (aArgs[1].IsEmpty())
{
OutputEnabledDisabledStatus(otIp6IsMulticastPromiscuousEnabled(GetInstancePtr()));
}
else
{
bool enable;
SuccessOrExit(error = ParseEnableOrDisable(aArgs[1], enable));
otIp6SetMulticastPromiscuousEnabled(GetInstancePtr(), enable);
}
}
else if (aArgs[0] == "llatn")
{
OutputIp6AddressLine(*otThreadGetLinkLocalAllThreadNodesMulticastAddress(GetInstancePtr()));
}
else if (aArgs[0] == "rlatn")
{
OutputIp6AddressLine(*otThreadGetRealmLocalAllThreadNodesMulticastAddress(GetInstancePtr()));
}
else
{
error = OT_ERROR_INVALID_COMMAND;
}
exit:
return error;
}
template <> otError Interpreter::Process<Cmd("keysequence")>(Arg aArgs[])
{
otError error = OT_ERROR_INVALID_ARGS;
if (aArgs[0] == "counter")
{
error = ProcessGetSet(aArgs + 1, otThreadGetKeySequenceCounter, otThreadSetKeySequenceCounter);
}
else if (aArgs[0] == "guardtime")
{
error = ProcessGetSet(aArgs + 1, otThreadGetKeySwitchGuardTime, otThreadSetKeySwitchGuardTime);
}
return error;
}
template <> otError Interpreter::Process<Cmd("leaderdata")>(Arg aArgs[])
{
OT_UNUSED_VARIABLE(aArgs);
otError error;
otLeaderData leaderData;
SuccessOrExit(error = otThreadGetLeaderData(GetInstancePtr(), &leaderData));
OutputLine("Partition ID: %u", leaderData.mPartitionId);
OutputLine("Weighting: %d", leaderData.mWeighting);
OutputLine("Data Version: %d", leaderData.mDataVersion);
OutputLine("Stable Data Version: %d", leaderData.mStableDataVersion);
OutputLine("Leader Router ID: %d", leaderData.mLeaderRouterId);
exit:
return error;
}
#if OPENTHREAD_FTD
template <> otError Interpreter::Process<Cmd("partitionid")>(Arg aArgs[])
{
otError error = OT_ERROR_INVALID_COMMAND;
if (aArgs[0].IsEmpty())
{
OutputLine("%u", otThreadGetPartitionId(GetInstancePtr()));
error = OT_ERROR_NONE;
}
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
else if (aArgs[0] == "preferred")
{
error = ProcessGetSet(aArgs + 1, otThreadGetPreferredLeaderPartitionId, otThreadSetPreferredLeaderPartitionId);
}
#endif
return error;
}
template <> otError Interpreter::Process<Cmd("leaderweight")>(Arg aArgs[])
{
return ProcessGetSet(aArgs, otThreadGetLocalLeaderWeight, otThreadSetLocalLeaderWeight);
}
#endif // OPENTHREAD_FTD
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE
void Interpreter::HandleLinkMetricsReport(const otIp6Address * aAddress,
const otLinkMetricsValues *aMetricsValues,
uint8_t aStatus,
void * aContext)
{
static_cast<Interpreter *>(aContext)->HandleLinkMetricsReport(aAddress, aMetricsValues, aStatus);
}
void Interpreter::PrintLinkMetricsValue(const otLinkMetricsValues *aMetricsValues)
{
const char kLinkMetricsTypeCount[] = "(Count/Summation)";
const char kLinkMetricsTypeAverage[] = "(Exponential Moving Average)";
if (aMetricsValues->mMetrics.mPduCount)
{
OutputLine(" - PDU Counter: %d %s", aMetricsValues->mPduCountValue, kLinkMetricsTypeCount);
}