/* * 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_output.hpp" #include #include #include #if OPENTHREAD_FTD || OPENTHREAD_MTD #include #endif #include #include "cli/cli.hpp" #include "common/string.hpp" namespace ot { namespace Cli { const char Output::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 Output::OutputFormat(const char *aFormat, ...) { va_list args; va_start(args, aFormat); OutputFormatV(aFormat, args); va_end(args); } void Output::OutputFormat(uint8_t aIndentSize, const char *aFormat, ...) { va_list args; OutputSpaces(aIndentSize); va_start(args, aFormat); OutputFormatV(aFormat, args); va_end(args); } void Output::OutputLine(const char *aFormat, ...) { va_list args; va_start(args, aFormat); OutputFormatV(aFormat, args); va_end(args); OutputNewLine(); } void Output::OutputLine(uint8_t aIndentSize, const char *aFormat, ...) { va_list args; OutputSpaces(aIndentSize); va_start(args, aFormat); OutputFormatV(aFormat, args); va_end(args); OutputNewLine(); } void Output::OutputNewLine(void) { OutputFormat("\r\n"); } void Output::OutputSpaces(uint8_t aCount) { OutputFormat("%*s", aCount, ""); } void Output::OutputBytes(const uint8_t *aBytes, uint16_t aLength) { for (uint16_t i = 0; i < aLength; i++) { OutputFormat("%02x", aBytes[i]); } } void Output::OutputBytesLine(const uint8_t *aBytes, uint16_t aLength) { OutputBytes(aBytes, aLength); OutputNewLine(); } const char *Output::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('0' + static_cast(aUint64 % 10)); } } return cur; } void Output::OutputUint64(uint64_t aUint64) { Uint64StringBuffer buffer; OutputFormat("%s", Uint64ToString(aUint64, buffer)); } void Output::OutputUint64Line(uint64_t aUint64) { OutputUint64(aUint64); OutputNewLine(); } void Output::OutputEnabledDisabledStatus(bool aEnabled) { OutputLine(aEnabled ? "Enabled" : "Disabled"); } #if OPENTHREAD_FTD || OPENTHREAD_MTD void Output::OutputIp6Address(const otIp6Address &aAddress) { char string[OT_IP6_ADDRESS_STRING_SIZE]; otIp6AddressToString(&aAddress, string, sizeof(string)); return OutputFormat("%s", string); } void Output::OutputIp6AddressLine(const otIp6Address &aAddress) { OutputIp6Address(aAddress); OutputNewLine(); } void Output::OutputIp6Prefix(const otIp6Prefix &aPrefix) { char string[OT_IP6_PREFIX_STRING_SIZE]; otIp6PrefixToString(&aPrefix, string, sizeof(string)); OutputFormat("%s", string); } void Output::OutputIp6PrefixLine(const otIp6Prefix &aPrefix) { OutputIp6Prefix(aPrefix); OutputNewLine(); } void Output::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 Output::OutputIp6PrefixLine(const otIp6NetworkPrefix &aPrefix) { OutputIp6Prefix(aPrefix); OutputNewLine(); } void Output::OutputSockAddr(const otSockAddr &aSockAddr) { char string[OT_IP6_SOCK_ADDR_STRING_SIZE]; otIp6SockAddrToString(&aSockAddr, string, sizeof(string)); return OutputFormat("%s", string); } void Output::OutputSockAddrLine(const otSockAddr &aSockAddr) { OutputSockAddr(aSockAddr); OutputNewLine(); } void Output::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 *Output::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(scaledValue / 100), static_cast(scaledValue % 100)); return aBuffer.mChars; } #endif // OPENTHREAD_FTD || OPENTHREAD_MTD void Output::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(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(&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 Output::LogInput(const Arg *aArgs) { String 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 Output::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(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(width), width, title); } } OutputLine("|"); OutputTableSeparator(aNumColumns, aWidths); } void Output::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("+"); } } // namespace Cli } // namespace ot