1 /*
2 * Copyright (c) 2021, The OpenThread Authors.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * 3. Neither the name of the copyright holder nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 /**
30 * @file
31 * This file contains implementation of the CLI output module.
32 */
33
34 #include "cli_output.hpp"
35
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39
40 #if OPENTHREAD_FTD || OPENTHREAD_MTD
41 #include <openthread/dns.h>
42 #endif
43 #include <openthread/logging.h>
44
45 #include "cli/cli.hpp"
46 #include "common/string.hpp"
47
48 namespace ot {
49 namespace Cli {
50
51 const char Output::kUnknownString[] = "unknown";
52
OutputImplementer(otCliOutputCallback aCallback,void * aCallbackContext)53 OutputImplementer::OutputImplementer(otCliOutputCallback aCallback, void *aCallbackContext)
54 : mCallback(aCallback)
55 , mCallbackContext(aCallbackContext)
56 #if OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE
57 , mOutputLength(0)
58 , mEmittingCommandOutput(true)
59 #endif
60 {
61 }
62
OutputFormat(const char * aFormat,...)63 void Output::OutputFormat(const char *aFormat, ...)
64 {
65 va_list args;
66
67 va_start(args, aFormat);
68 OutputFormatV(aFormat, args);
69 va_end(args);
70 }
71
OutputFormat(uint8_t aIndentSize,const char * aFormat,...)72 void Output::OutputFormat(uint8_t aIndentSize, const char *aFormat, ...)
73 {
74 va_list args;
75
76 OutputSpaces(aIndentSize);
77
78 va_start(args, aFormat);
79 OutputFormatV(aFormat, args);
80 va_end(args);
81 }
82
OutputLine(const char * aFormat,...)83 void Output::OutputLine(const char *aFormat, ...)
84 {
85 va_list args;
86
87 va_start(args, aFormat);
88 OutputFormatV(aFormat, args);
89 va_end(args);
90
91 OutputNewLine();
92 }
93
OutputLine(uint8_t aIndentSize,const char * aFormat,...)94 void Output::OutputLine(uint8_t aIndentSize, const char *aFormat, ...)
95 {
96 va_list args;
97
98 OutputSpaces(aIndentSize);
99
100 va_start(args, aFormat);
101 OutputFormatV(aFormat, args);
102 va_end(args);
103
104 OutputNewLine();
105 }
106
OutputNewLine(void)107 void Output::OutputNewLine(void) { OutputFormat("\r\n"); }
108
OutputSpaces(uint8_t aCount)109 void Output::OutputSpaces(uint8_t aCount) { OutputFormat("%*s", aCount, ""); }
110
OutputBytes(const uint8_t * aBytes,uint16_t aLength)111 void Output::OutputBytes(const uint8_t *aBytes, uint16_t aLength)
112 {
113 for (uint16_t i = 0; i < aLength; i++)
114 {
115 OutputFormat("%02x", aBytes[i]);
116 }
117 }
118
OutputBytesLine(const uint8_t * aBytes,uint16_t aLength)119 void Output::OutputBytesLine(const uint8_t *aBytes, uint16_t aLength)
120 {
121 OutputBytes(aBytes, aLength);
122 OutputNewLine();
123 }
124
Uint64ToString(uint64_t aUint64,Uint64StringBuffer & aBuffer)125 const char *Output::Uint64ToString(uint64_t aUint64, Uint64StringBuffer &aBuffer)
126 {
127 char *cur = &aBuffer.mChars[Uint64StringBuffer::kSize - 1];
128
129 *cur = '\0';
130
131 if (aUint64 == 0)
132 {
133 *(--cur) = '0';
134 }
135 else
136 {
137 for (; aUint64 != 0; aUint64 /= 10)
138 {
139 *(--cur) = static_cast<char>('0' + static_cast<uint8_t>(aUint64 % 10));
140 }
141 }
142
143 return cur;
144 }
145
OutputUint64(uint64_t aUint64)146 void Output::OutputUint64(uint64_t aUint64)
147 {
148 Uint64StringBuffer buffer;
149
150 OutputFormat("%s", Uint64ToString(aUint64, buffer));
151 }
152
OutputUint64Line(uint64_t aUint64)153 void Output::OutputUint64Line(uint64_t aUint64)
154 {
155 OutputUint64(aUint64);
156 OutputNewLine();
157 }
158
OutputEnabledDisabledStatus(bool aEnabled)159 void Output::OutputEnabledDisabledStatus(bool aEnabled) { OutputLine(aEnabled ? "Enabled" : "Disabled"); }
160
161 #if OPENTHREAD_FTD || OPENTHREAD_MTD
162
OutputIp6Address(const otIp6Address & aAddress)163 void Output::OutputIp6Address(const otIp6Address &aAddress)
164 {
165 char string[OT_IP6_ADDRESS_STRING_SIZE];
166
167 otIp6AddressToString(&aAddress, string, sizeof(string));
168
169 return OutputFormat("%s", string);
170 }
171
OutputIp6AddressLine(const otIp6Address & aAddress)172 void Output::OutputIp6AddressLine(const otIp6Address &aAddress)
173 {
174 OutputIp6Address(aAddress);
175 OutputNewLine();
176 }
177
OutputIp6Prefix(const otIp6Prefix & aPrefix)178 void Output::OutputIp6Prefix(const otIp6Prefix &aPrefix)
179 {
180 char string[OT_IP6_PREFIX_STRING_SIZE];
181
182 otIp6PrefixToString(&aPrefix, string, sizeof(string));
183
184 OutputFormat("%s", string);
185 }
186
OutputIp6PrefixLine(const otIp6Prefix & aPrefix)187 void Output::OutputIp6PrefixLine(const otIp6Prefix &aPrefix)
188 {
189 OutputIp6Prefix(aPrefix);
190 OutputNewLine();
191 }
192
OutputIp6Prefix(const otIp6NetworkPrefix & aPrefix)193 void Output::OutputIp6Prefix(const otIp6NetworkPrefix &aPrefix)
194 {
195 OutputFormat("%x:%x:%x:%x::/64", (aPrefix.m8[0] << 8) | aPrefix.m8[1], (aPrefix.m8[2] << 8) | aPrefix.m8[3],
196 (aPrefix.m8[4] << 8) | aPrefix.m8[5], (aPrefix.m8[6] << 8) | aPrefix.m8[7]);
197 }
198
OutputIp6PrefixLine(const otIp6NetworkPrefix & aPrefix)199 void Output::OutputIp6PrefixLine(const otIp6NetworkPrefix &aPrefix)
200 {
201 OutputIp6Prefix(aPrefix);
202 OutputNewLine();
203 }
204
OutputSockAddr(const otSockAddr & aSockAddr)205 void Output::OutputSockAddr(const otSockAddr &aSockAddr)
206 {
207 char string[OT_IP6_SOCK_ADDR_STRING_SIZE];
208
209 otIp6SockAddrToString(&aSockAddr, string, sizeof(string));
210
211 return OutputFormat("%s", string);
212 }
213
OutputSockAddrLine(const otSockAddr & aSockAddr)214 void Output::OutputSockAddrLine(const otSockAddr &aSockAddr)
215 {
216 OutputSockAddr(aSockAddr);
217 OutputNewLine();
218 }
219
OutputDnsTxtData(const uint8_t * aTxtData,uint16_t aTxtDataLength)220 void Output::OutputDnsTxtData(const uint8_t *aTxtData, uint16_t aTxtDataLength)
221 {
222 otDnsTxtEntry entry;
223 otDnsTxtEntryIterator iterator;
224 bool isFirst = true;
225
226 otDnsInitTxtEntryIterator(&iterator, aTxtData, aTxtDataLength);
227
228 OutputFormat("[");
229
230 while (otDnsGetNextTxtEntry(&iterator, &entry) == OT_ERROR_NONE)
231 {
232 if (!isFirst)
233 {
234 OutputFormat(", ");
235 }
236
237 if (entry.mKey == nullptr)
238 {
239 // A null `mKey` indicates that the key in the entry is
240 // longer than the recommended max key length, so the entry
241 // could not be parsed. In this case, the whole entry is
242 // returned encoded in `mValue`.
243
244 OutputFormat("[");
245 OutputBytes(entry.mValue, entry.mValueLength);
246 OutputFormat("]");
247 }
248 else
249 {
250 OutputFormat("%s", entry.mKey);
251
252 if (entry.mValue != nullptr)
253 {
254 OutputFormat("=");
255 OutputBytes(entry.mValue, entry.mValueLength);
256 }
257 }
258
259 isFirst = false;
260 }
261
262 OutputFormat("]");
263 }
264
PercentageToString(uint16_t aValue,PercentageStringBuffer & aBuffer)265 const char *Output::PercentageToString(uint16_t aValue, PercentageStringBuffer &aBuffer)
266 {
267 uint32_t scaledValue = aValue;
268 StringWriter writer(aBuffer.mChars, sizeof(aBuffer.mChars));
269
270 scaledValue = (scaledValue * 10000) / 0xffff;
271 writer.Append("%u.%02u", static_cast<uint16_t>(scaledValue / 100), static_cast<uint16_t>(scaledValue % 100));
272
273 return aBuffer.mChars;
274 }
275
276 #endif // OPENTHREAD_FTD || OPENTHREAD_MTD
277
OutputFormatV(const char * aFormat,va_list aArguments)278 void Output::OutputFormatV(const char *aFormat, va_list aArguments) { mImplementer.OutputV(aFormat, aArguments); }
279
OutputV(const char * aFormat,va_list aArguments)280 void OutputImplementer::OutputV(const char *aFormat, va_list aArguments)
281 {
282 #if OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE
283 va_list args;
284 int charsWritten;
285 bool truncated = false;
286
287 va_copy(args, aArguments);
288 #endif
289
290 mCallback(mCallbackContext, aFormat, aArguments);
291
292 #if OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE
293 VerifyOrExit(mEmittingCommandOutput);
294
295 charsWritten = vsnprintf(&mOutputString[mOutputLength], sizeof(mOutputString) - mOutputLength, aFormat, args);
296
297 VerifyOrExit(charsWritten >= 0, mOutputLength = 0);
298
299 if (static_cast<uint32_t>(charsWritten) >= sizeof(mOutputString) - mOutputLength)
300 {
301 truncated = true;
302 mOutputLength = sizeof(mOutputString) - 1;
303 }
304 else
305 {
306 mOutputLength += charsWritten;
307 }
308
309 while (true)
310 {
311 char *lineEnd = strchr(mOutputString, '\r');
312
313 if (lineEnd == nullptr)
314 {
315 break;
316 }
317
318 *lineEnd = '\0';
319
320 if (lineEnd > mOutputString)
321 {
322 otLogCli(OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_LEVEL, "Output: %s", mOutputString);
323 }
324
325 lineEnd++;
326
327 while ((*lineEnd == '\n') || (*lineEnd == '\r'))
328 {
329 lineEnd++;
330 }
331
332 // Example of the pointers and lengths.
333 //
334 // - mOutputString = "hi\r\nmore"
335 // - mOutputLength = 8
336 // - lineEnd = &mOutputString[4]
337 //
338 //
339 // 0 1 2 3 4 5 6 7 8 9
340 // +----+----+----+----+----+----+----+----+----+---
341 // | h | i | \r | \n | m | o | r | e | \0 |
342 // +----+----+----+----+----+----+----+----+----+---
343 // ^ ^
344 // | |
345 // lineEnd mOutputString[mOutputLength]
346 //
347 //
348 // New length is `&mOutputString[8] - &mOutputString[4] -> 4`.
349 //
350 // We move (newLen + 1 = 5) chars from `lineEnd` to start of
351 // `mOutputString` which will include the `\0` char.
352 //
353 // If `lineEnd` and `mOutputString[mOutputLength]` are the same
354 // the code works correctly as well (new length set to zero and
355 // the `\0` is copied).
356
357 mOutputLength = static_cast<uint16_t>(&mOutputString[mOutputLength] - lineEnd);
358 memmove(mOutputString, lineEnd, mOutputLength + 1);
359 }
360
361 if (truncated)
362 {
363 otLogCli(OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_LEVEL, "Output: %s ...", mOutputString);
364 mOutputLength = 0;
365 }
366
367 exit:
368 va_end(args);
369 #endif // OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE
370 }
371
372 #if OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE
LogInput(const Arg * aArgs)373 void Output::LogInput(const Arg *aArgs)
374 {
375 String<kInputOutputLogStringSize> inputString;
376
377 for (bool isFirst = true; !aArgs->IsEmpty(); aArgs++, isFirst = false)
378 {
379 inputString.Append(isFirst ? "%s" : " %s", aArgs->GetCString());
380 }
381
382 otLogCli(OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_LEVEL, "Input: %s", inputString.AsCString());
383 }
384 #endif
385
OutputTableHeader(uint8_t aNumColumns,const char * const aTitles[],const uint8_t aWidths[])386 void Output::OutputTableHeader(uint8_t aNumColumns, const char *const aTitles[], const uint8_t aWidths[])
387 {
388 for (uint8_t index = 0; index < aNumColumns; index++)
389 {
390 const char *title = aTitles[index];
391 uint8_t width = aWidths[index];
392 size_t titleLength = strlen(title);
393
394 if (titleLength + 2 <= width)
395 {
396 // `title` fits in column width so we write it with extra space
397 // at beginning and end ("| Title |").
398
399 OutputFormat("| %*s", -static_cast<int>(width - 1), title);
400 }
401 else
402 {
403 // Use narrow style (no space at beginning) and write as many
404 // chars from `title` as it can fit in the given column width
405 // ("|Title|").
406
407 OutputFormat("|%*.*s", -static_cast<int>(width), width, title);
408 }
409 }
410
411 OutputLine("|");
412 OutputTableSeparator(aNumColumns, aWidths);
413 }
414
OutputTableSeparator(uint8_t aNumColumns,const uint8_t aWidths[])415 void Output::OutputTableSeparator(uint8_t aNumColumns, const uint8_t aWidths[])
416 {
417 for (uint8_t index = 0; index < aNumColumns; index++)
418 {
419 OutputFormat("+");
420
421 for (uint8_t width = aWidths[index]; width != 0; width--)
422 {
423 OutputFormat("-");
424 }
425 }
426
427 OutputLine("+");
428 }
429
430 } // namespace Cli
431 } // namespace ot
432