blob: c2ee2c2dca6ab9910d48093a578bf4118a31d3f6 [file] [log] [blame]
/*
* Copyright (c) 2021, 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 contains implementation of the CLI output module.
*/
#include "cli_utils.hpp"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if OPENTHREAD_FTD || OPENTHREAD_MTD
#include <openthread/dns.h>
#endif
#include <openthread/logging.h>
#include "cli/cli.hpp"
#include "common/string.hpp"
namespace ot {
namespace Cli {
const char Utils::kUnknownString[] = "unknown";
OutputImplementer::OutputImplementer(otCliOutputCallback aCallback, void *aCallbackContext)
: mCallback(aCallback)
, mCallbackContext(aCallbackContext)
#if OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE
, mOutputLength(0)
, mEmittingCommandOutput(true)
#endif
{
}
void Utils::OutputFormat(const char *aFormat, ...)
{
va_list args;
va_start(args, aFormat);
OutputFormatV(aFormat, args);
va_end(args);
}
void Utils::OutputFormat(uint8_t aIndentSize, const char *aFormat, ...)
{
va_list args;
OutputSpaces(aIndentSize);
va_start(args, aFormat);
OutputFormatV(aFormat, args);
va_end(args);
}
void Utils::OutputLine(const char *aFormat, ...)
{
va_list args;
va_start(args, aFormat);
OutputFormatV(aFormat, args);
va_end(args);
OutputNewLine();
}
void Utils::OutputLine(uint8_t aIndentSize, const char *aFormat, ...)
{
va_list args;
OutputSpaces(aIndentSize);
va_start(args, aFormat);
OutputFormatV(aFormat, args);
va_end(args);
OutputNewLine();
}
void Utils::OutputNewLine(void) { OutputFormat("\r\n"); }
void Utils::OutputSpaces(uint8_t aCount) { OutputFormat("%*s", aCount, ""); }
void Utils::OutputBytes(const uint8_t *aBytes, uint16_t aLength)
{
for (uint16_t i = 0; i < aLength; i++)
{
OutputFormat("%02x", aBytes[i]);
}
}
void Utils::OutputBytesLine(const uint8_t *aBytes, uint16_t aLength)
{
OutputBytes(aBytes, aLength);
OutputNewLine();
}
const char *Utils::Uint64ToString(uint64_t aUint64, Uint64StringBuffer &aBuffer)
{
char *cur = &aBuffer.mChars[Uint64StringBuffer::kSize - 1];
*cur = '\0';
if (aUint64 == 0)
{
*(--cur) = '0';
}
else
{
for (; aUint64 != 0; aUint64 /= 10)
{
*(--cur) = static_cast<char>('0' + static_cast<uint8_t>(aUint64 % 10));
}
}
return cur;
}
void Utils::OutputUint64(uint64_t aUint64)
{
Uint64StringBuffer buffer;
OutputFormat("%s", Uint64ToString(aUint64, buffer));
}
void Utils::OutputUint64Line(uint64_t aUint64)
{
OutputUint64(aUint64);
OutputNewLine();
}
void Utils::OutputEnabledDisabledStatus(bool aEnabled) { OutputLine(aEnabled ? "Enabled" : "Disabled"); }
#if OPENTHREAD_FTD || OPENTHREAD_MTD
void Utils::OutputIp6Address(const otIp6Address &aAddress)
{
char string[OT_IP6_ADDRESS_STRING_SIZE];
otIp6AddressToString(&aAddress, string, sizeof(string));
return OutputFormat("%s", string);
}
void Utils::OutputIp6AddressLine(const otIp6Address &aAddress)
{
OutputIp6Address(aAddress);
OutputNewLine();
}
void Utils::OutputIp6Prefix(const otIp6Prefix &aPrefix)
{
char string[OT_IP6_PREFIX_STRING_SIZE];
otIp6PrefixToString(&aPrefix, string, sizeof(string));
OutputFormat("%s", string);
}
void Utils::OutputIp6PrefixLine(const otIp6Prefix &aPrefix)
{
OutputIp6Prefix(aPrefix);
OutputNewLine();
}
void Utils::OutputIp6Prefix(const otIp6NetworkPrefix &aPrefix)
{
OutputFormat("%x:%x:%x:%x::/64", (aPrefix.m8[0] << 8) | aPrefix.m8[1], (aPrefix.m8[2] << 8) | aPrefix.m8[3],
(aPrefix.m8[4] << 8) | aPrefix.m8[5], (aPrefix.m8[6] << 8) | aPrefix.m8[7]);
}
void Utils::OutputIp6PrefixLine(const otIp6NetworkPrefix &aPrefix)
{
OutputIp6Prefix(aPrefix);
OutputNewLine();
}
void Utils::OutputSockAddr(const otSockAddr &aSockAddr)
{
char string[OT_IP6_SOCK_ADDR_STRING_SIZE];
otIp6SockAddrToString(&aSockAddr, string, sizeof(string));
return OutputFormat("%s", string);
}
void Utils::OutputSockAddrLine(const otSockAddr &aSockAddr)
{
OutputSockAddr(aSockAddr);
OutputNewLine();
}
void Utils::OutputDnsTxtData(const uint8_t *aTxtData, uint16_t aTxtDataLength)
{
otDnsTxtEntry entry;
otDnsTxtEntryIterator iterator;
bool isFirst = true;
otDnsInitTxtEntryIterator(&iterator, aTxtData, aTxtDataLength);
OutputFormat("[");
while (otDnsGetNextTxtEntry(&iterator, &entry) == OT_ERROR_NONE)
{
if (!isFirst)
{
OutputFormat(", ");
}
if (entry.mKey == nullptr)
{
// A null `mKey` indicates that the key in the entry is
// longer than the recommended max key length, so the entry
// could not be parsed. In this case, the whole entry is
// returned encoded in `mValue`.
OutputFormat("[");
OutputBytes(entry.mValue, entry.mValueLength);
OutputFormat("]");
}
else
{
OutputFormat("%s", entry.mKey);
if (entry.mValue != nullptr)
{
OutputFormat("=");
OutputBytes(entry.mValue, entry.mValueLength);
}
}
isFirst = false;
}
OutputFormat("]");
}
const char *Utils::PercentageToString(uint16_t aValue, PercentageStringBuffer &aBuffer)
{
uint32_t scaledValue = aValue;
StringWriter writer(aBuffer.mChars, sizeof(aBuffer.mChars));
scaledValue = (scaledValue * 10000) / 0xffff;
writer.Append("%u.%02u", static_cast<uint16_t>(scaledValue / 100), static_cast<uint16_t>(scaledValue % 100));
return aBuffer.mChars;
}
#endif // OPENTHREAD_FTD || OPENTHREAD_MTD
void Utils::OutputFormatV(const char *aFormat, va_list aArguments) { mImplementer.OutputV(aFormat, aArguments); }
void OutputImplementer::OutputV(const char *aFormat, va_list aArguments)
{
#if OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE
va_list args;
int charsWritten;
bool truncated = false;
va_copy(args, aArguments);
#endif
mCallback(mCallbackContext, aFormat, aArguments);
#if OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE
VerifyOrExit(mEmittingCommandOutput);
charsWritten = vsnprintf(&mOutputString[mOutputLength], sizeof(mOutputString) - mOutputLength, aFormat, args);
VerifyOrExit(charsWritten >= 0, mOutputLength = 0);
if (static_cast<uint32_t>(charsWritten) >= sizeof(mOutputString) - mOutputLength)
{
truncated = true;
mOutputLength = sizeof(mOutputString) - 1;
}
else
{
mOutputLength += charsWritten;
}
while (true)
{
char *lineEnd = strchr(mOutputString, '\r');
if (lineEnd == nullptr)
{
break;
}
*lineEnd = '\0';
if (lineEnd > mOutputString)
{
otLogCli(OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_LEVEL, "Output: %s", mOutputString);
}
lineEnd++;
while ((*lineEnd == '\n') || (*lineEnd == '\r'))
{
lineEnd++;
}
// Example of the pointers and lengths.
//
// - mOutputString = "hi\r\nmore"
// - mOutputLength = 8
// - lineEnd = &mOutputString[4]
//
//
// 0 1 2 3 4 5 6 7 8 9
// +----+----+----+----+----+----+----+----+----+---
// | h | i | \r | \n | m | o | r | e | \0 |
// +----+----+----+----+----+----+----+----+----+---
// ^ ^
// | |
// lineEnd mOutputString[mOutputLength]
//
//
// New length is `&mOutputString[8] - &mOutputString[4] -> 4`.
//
// We move (newLen + 1 = 5) chars from `lineEnd` to start of
// `mOutputString` which will include the `\0` char.
//
// If `lineEnd` and `mOutputString[mOutputLength]` are the same
// the code works correctly as well (new length set to zero and
// the `\0` is copied).
mOutputLength = static_cast<uint16_t>(&mOutputString[mOutputLength] - lineEnd);
memmove(mOutputString, lineEnd, mOutputLength + 1);
}
if (truncated)
{
otLogCli(OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_LEVEL, "Output: %s ...", mOutputString);
mOutputLength = 0;
}
exit:
va_end(args);
#endif // OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE
}
#if OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE
void Utils::LogInput(const Arg *aArgs)
{
String<kInputOutputLogStringSize> inputString;
for (bool isFirst = true; !aArgs->IsEmpty(); aArgs++, isFirst = false)
{
inputString.Append(isFirst ? "%s" : " %s", aArgs->GetCString());
}
otLogCli(OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_LEVEL, "Input: %s", inputString.AsCString());
}
#endif
void Utils::OutputTableHeader(uint8_t aNumColumns, const char *const aTitles[], const uint8_t aWidths[])
{
for (uint8_t index = 0; index < aNumColumns; index++)
{
const char *title = aTitles[index];
uint8_t width = aWidths[index];
size_t titleLength = strlen(title);
if (titleLength + 2 <= width)
{
// `title` fits in column width so we write it with extra space
// at beginning and end ("| Title |").
OutputFormat("| %*s", -static_cast<int>(width - 1), title);
}
else
{
// Use narrow style (no space at beginning) and write as many
// chars from `title` as it can fit in the given column width
// ("|Title|").
OutputFormat("|%*.*s", -static_cast<int>(width), width, title);
}
}
OutputLine("|");
OutputTableSeparator(aNumColumns, aWidths);
}
void Utils::OutputTableSeparator(uint8_t aNumColumns, const uint8_t aWidths[])
{
for (uint8_t index = 0; index < aNumColumns; index++)
{
OutputFormat("+");
for (uint8_t width = aWidths[index]; width != 0; width--)
{
OutputFormat("-");
}
}
OutputLine("+");
}
otError Utils::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 Utils::ProcessEnableDisable(Arg aArgs[], SetEnabledHandler aSetEnabledHandler)
{
otError error = OT_ERROR_NONE;
bool enable;
if (ParseEnableOrDisable(aArgs[0], enable) == OT_ERROR_NONE)
{
aSetEnabledHandler(GetInstancePtr(), enable);
}
else
{
error = OT_ERROR_INVALID_COMMAND;
}
return error;
}
otError Utils::ProcessEnableDisable(Arg aArgs[], SetEnabledHandlerFailable aSetEnabledHandler)
{
otError error = OT_ERROR_NONE;
bool enable;
if (ParseEnableOrDisable(aArgs[0], enable) == OT_ERROR_NONE)
{
error = aSetEnabledHandler(GetInstancePtr(), enable);
}
else
{
error = OT_ERROR_INVALID_COMMAND;
}
return error;
}
otError Utils::ProcessEnableDisable(Arg aArgs[],
IsEnabledHandler aIsEnabledHandler,
SetEnabledHandler aSetEnabledHandler)
{
otError error = OT_ERROR_NONE;
if (aArgs[0].IsEmpty())
{
OutputEnabledDisabledStatus(aIsEnabledHandler(GetInstancePtr()));
}
else
{
error = ProcessEnableDisable(aArgs, aSetEnabledHandler);
}
return error;
}
otError Utils::ProcessEnableDisable(Arg aArgs[],
IsEnabledHandler aIsEnabledHandler,
SetEnabledHandlerFailable aSetEnabledHandler)
{
otError error = OT_ERROR_NONE;
if (aArgs[0].IsEmpty())
{
OutputEnabledDisabledStatus(aIsEnabledHandler(GetInstancePtr()));
}
else
{
error = ProcessEnableDisable(aArgs, aSetEnabledHandler);
}
return error;
}
otError Utils::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 = ot::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;
}
otError Utils::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 *Utils::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_FTD || OPENTHREAD_MTD
otError Utils::ParseToIp6Address(otInstance *aInstance, const Arg &aArg, otIp6Address &aAddress, bool &aSynthesized)
{
Error error = OT_ERROR_NONE;
VerifyOrExit(!aArg.IsEmpty(), error = OT_ERROR_INVALID_ARGS);
error = aArg.ParseAsIp6Address(aAddress);
aSynthesized = false;
if (error != OT_ERROR_NONE)
{
// It might be an IPv4 address, let's have a try.
otIp4Address ip4Address;
// Do not touch the error value if we failed to parse it as an IPv4 address.
SuccessOrExit(aArg.ParseAsIp4Address(ip4Address));
SuccessOrExit(error = otNat64SynthesizeIp6Address(aInstance, &ip4Address, &aAddress));
aSynthesized = true;
}
exit:
return error;
}
#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
otError Utils::ParsePrefix(Arg aArgs[], otBorderRouterConfig &aConfig)
{
otError error = OT_ERROR_NONE;
ClearAllBytes(aConfig);
SuccessOrExit(error = aArgs[0].ParseAsIp6Prefix(aConfig.mPrefix));
aArgs++;
for (; !aArgs->IsEmpty(); aArgs++)
{
otRoutePreference preference;
if (ParsePreference(*aArgs, preference) == OT_ERROR_NONE)
{
aConfig.mPreference = preference;
}
else
{
for (char *arg = aArgs->GetCString(); *arg != '\0'; arg++)
{
switch (*arg)
{
case 'p':
aConfig.mPreferred = true;
break;
case 'a':
aConfig.mSlaac = true;
break;
case 'd':
aConfig.mDhcp = true;
break;
case 'c':
aConfig.mConfigure = true;
break;
case 'r':
aConfig.mDefaultRoute = true;
break;
case 'o':
aConfig.mOnMesh = true;
break;
case 's':
aConfig.mStable = true;
break;
case 'n':
aConfig.mNdDns = true;
break;
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
case 'D':
aConfig.mDp = true;
break;
#endif
case '-':
break;
default:
ExitNow(error = OT_ERROR_INVALID_ARGS);
}
}
}
}
exit:
return error;
}
otError Utils::ParseRoute(Arg aArgs[], otExternalRouteConfig &aConfig)
{
otError error = OT_ERROR_NONE;
ClearAllBytes(aConfig);
SuccessOrExit(error = aArgs[0].ParseAsIp6Prefix(aConfig.mPrefix));
aArgs++;
for (; !aArgs->IsEmpty(); aArgs++)
{
otRoutePreference preference;
if (ParsePreference(*aArgs, preference) == OT_ERROR_NONE)
{
aConfig.mPreference = preference;
}
else
{
for (char *arg = aArgs->GetCString(); *arg != '\0'; arg++)
{
switch (*arg)
{
case 's':
aConfig.mStable = true;
break;
case 'n':
aConfig.mNat64 = true;
break;
case 'a':
aConfig.mAdvPio = true;
break;
case '-':
break;
default:
ExitNow(error = OT_ERROR_INVALID_ARGS);
}
}
}
}
exit:
return error;
}
#endif // OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
#endif // #if OPENTHREAD_FTD || OPENTHREAD_MTD
const char *Utils::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;
}
const char *Utils::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);
}
} // namespace Cli
} // namespace ot