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 "common/string.hpp"
46
47 namespace ot {
48 namespace Cli {
49
50 const char OutputBase::kUnknownString[] = "unknown";
51
Output(otInstance * aInstance,otCliOutputCallback aCallback,void * aCallbackContext)52 Output::Output(otInstance *aInstance, otCliOutputCallback aCallback, void *aCallbackContext)
53 : mInstance(aInstance)
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 OutputFormat("\r\n");
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 OutputFormat("\r\n");
105 }
106
OutputSpaces(uint8_t aCount)107 void Output::OutputSpaces(uint8_t aCount)
108 {
109 char format[sizeof("%256s")];
110
111 snprintf(format, sizeof(format), "%%%us", aCount);
112
113 OutputFormat(format, "");
114 }
115
OutputBytes(const uint8_t * aBytes,uint16_t aLength)116 void Output::OutputBytes(const uint8_t *aBytes, uint16_t aLength)
117 {
118 for (uint16_t i = 0; i < aLength; i++)
119 {
120 OutputFormat("%02x", aBytes[i]);
121 }
122 }
123
OutputBytesLine(const uint8_t * aBytes,uint16_t aLength)124 void Output::OutputBytesLine(const uint8_t *aBytes, uint16_t aLength)
125 {
126 OutputBytes(aBytes, aLength);
127 OutputLine("");
128 }
129
OutputEnabledDisabledStatus(bool aEnabled)130 void Output::OutputEnabledDisabledStatus(bool aEnabled)
131 {
132 OutputLine(aEnabled ? "Enabled" : "Disabled");
133 }
134
135 #if OPENTHREAD_FTD || OPENTHREAD_MTD
136
OutputIp6Address(const otIp6Address & aAddress)137 void Output::OutputIp6Address(const otIp6Address &aAddress)
138 {
139 char string[OT_IP6_ADDRESS_STRING_SIZE];
140
141 otIp6AddressToString(&aAddress, string, sizeof(string));
142
143 return OutputFormat("%s", string);
144 }
145
OutputIp6AddressLine(const otIp6Address & aAddress)146 void Output::OutputIp6AddressLine(const otIp6Address &aAddress)
147 {
148 OutputIp6Address(aAddress);
149 OutputLine("");
150 }
151
OutputIp6Prefix(const otIp6Prefix & aPrefix)152 void Output::OutputIp6Prefix(const otIp6Prefix &aPrefix)
153 {
154 char string[OT_IP6_PREFIX_STRING_SIZE];
155
156 otIp6PrefixToString(&aPrefix, string, sizeof(string));
157
158 OutputFormat("%s", string);
159 }
160
OutputIp6PrefixLine(const otIp6Prefix & aPrefix)161 void Output::OutputIp6PrefixLine(const otIp6Prefix &aPrefix)
162 {
163 OutputIp6Prefix(aPrefix);
164 OutputLine("");
165 }
166
OutputIp6Prefix(const otIp6NetworkPrefix & aPrefix)167 void Output::OutputIp6Prefix(const otIp6NetworkPrefix &aPrefix)
168 {
169 OutputFormat("%x:%x:%x:%x::/64", (aPrefix.m8[0] << 8) | aPrefix.m8[1], (aPrefix.m8[2] << 8) | aPrefix.m8[3],
170 (aPrefix.m8[4] << 8) | aPrefix.m8[5], (aPrefix.m8[6] << 8) | aPrefix.m8[7]);
171 }
172
OutputIp6PrefixLine(const otIp6NetworkPrefix & aPrefix)173 void Output::OutputIp6PrefixLine(const otIp6NetworkPrefix &aPrefix)
174 {
175 OutputIp6Prefix(aPrefix);
176 OutputLine("");
177 }
178
OutputSockAddr(const otSockAddr & aSockAddr)179 void Output::OutputSockAddr(const otSockAddr &aSockAddr)
180 {
181 char string[OT_IP6_SOCK_ADDR_STRING_SIZE];
182
183 otIp6SockAddrToString(&aSockAddr, string, sizeof(string));
184
185 return OutputFormat("%s", string);
186 }
187
OutputSockAddrLine(const otSockAddr & aSockAddr)188 void Output::OutputSockAddrLine(const otSockAddr &aSockAddr)
189 {
190 OutputSockAddr(aSockAddr);
191 OutputLine("");
192 }
193
OutputDnsTxtData(const uint8_t * aTxtData,uint16_t aTxtDataLength)194 void Output::OutputDnsTxtData(const uint8_t *aTxtData, uint16_t aTxtDataLength)
195 {
196 otDnsTxtEntry entry;
197 otDnsTxtEntryIterator iterator;
198 bool isFirst = true;
199
200 otDnsInitTxtEntryIterator(&iterator, aTxtData, aTxtDataLength);
201
202 OutputFormat("[");
203
204 while (otDnsGetNextTxtEntry(&iterator, &entry) == OT_ERROR_NONE)
205 {
206 if (!isFirst)
207 {
208 OutputFormat(", ");
209 }
210
211 if (entry.mKey == nullptr)
212 {
213 // A null `mKey` indicates that the key in the entry is
214 // longer than the recommended max key length, so the entry
215 // could not be parsed. In this case, the whole entry is
216 // returned encoded in `mValue`.
217
218 OutputFormat("[");
219 OutputBytes(entry.mValue, entry.mValueLength);
220 OutputFormat("]");
221 }
222 else
223 {
224 OutputFormat("%s", entry.mKey);
225
226 if (entry.mValue != nullptr)
227 {
228 OutputFormat("=");
229 OutputBytes(entry.mValue, entry.mValueLength);
230 }
231 }
232
233 isFirst = false;
234 }
235
236 OutputFormat("]");
237 }
238 #endif // OPENTHREAD_FTD || OPENTHREAD_MTD
239
OutputFormatV(const char * aFormat,va_list aArguments)240 void Output::OutputFormatV(const char *aFormat, va_list aArguments)
241 {
242 #if OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE
243 va_list args;
244 int charsWritten;
245 bool truncated = false;
246
247 va_copy(args, aArguments);
248 #endif
249
250 mCallback(mCallbackContext, aFormat, aArguments);
251
252 #if OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE
253 VerifyOrExit(mEmittingCommandOutput);
254
255 charsWritten = vsnprintf(&mOutputString[mOutputLength], sizeof(mOutputString) - mOutputLength, aFormat, args);
256
257 VerifyOrExit(charsWritten >= 0, mOutputLength = 0);
258
259 if (static_cast<uint32_t>(charsWritten) >= sizeof(mOutputString) - mOutputLength)
260 {
261 truncated = true;
262 mOutputLength = sizeof(mOutputString) - 1;
263 }
264 else
265 {
266 mOutputLength += charsWritten;
267 }
268
269 while (true)
270 {
271 char *lineEnd = strchr(mOutputString, '\r');
272
273 if (lineEnd == nullptr)
274 {
275 break;
276 }
277
278 *lineEnd = '\0';
279
280 if (lineEnd > mOutputString)
281 {
282 otLogCli(OT_LOG_LEVEL_DEBG, "Output: %s", mOutputString);
283 }
284
285 lineEnd++;
286
287 while ((*lineEnd == '\n') || (*lineEnd == '\r'))
288 {
289 lineEnd++;
290 }
291
292 // Example of the pointers and lengths.
293 //
294 // - mOutputString = "hi\r\nmore"
295 // - mOutputLength = 8
296 // - lineEnd = &mOutputString[4]
297 //
298 //
299 // 0 1 2 3 4 5 6 7 8 9
300 // +----+----+----+----+----+----+----+----+----+---
301 // | h | i | \r | \n | m | o | r | e | \0 |
302 // +----+----+----+----+----+----+----+----+----+---
303 // ^ ^
304 // | |
305 // lineEnd mOutputString[mOutputLength]
306 //
307 //
308 // New length is `&mOutputString[8] - &mOutputString[4] -> 4`.
309 //
310 // We move (newLen + 1 = 5) chars from `lineEnd` to start of
311 // `mOutputString` which will include the `\0` char.
312 //
313 // If `lineEnd` and `mOutputString[mOutputLength]` are the same
314 // the code works correctly as well (new length set to zero and
315 // the `\0` is copied).
316
317 mOutputLength = static_cast<uint16_t>(&mOutputString[mOutputLength] - lineEnd);
318 memmove(mOutputString, lineEnd, mOutputLength + 1);
319 }
320
321 if (truncated)
322 {
323 otLogCli(OT_LOG_LEVEL_DEBG, "Output: %s ...", mOutputString);
324 mOutputLength = 0;
325 }
326
327 exit:
328 va_end(args);
329 #endif // OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE
330 }
331
332 #if OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE
LogInput(const Arg * aArgs)333 void Output::LogInput(const Arg *aArgs)
334 {
335 String<kInputOutputLogStringSize> inputString;
336
337 for (bool isFirst = true; !aArgs->IsEmpty(); aArgs++, isFirst = false)
338 {
339 inputString.Append(isFirst ? "%s" : " %s", aArgs->GetCString());
340 }
341
342 otLogCli(OT_LOG_LEVEL_DEBG, "Input: %s", inputString.AsCString());
343 }
344 #endif
345
OutputTableHeader(uint8_t aNumColumns,const char * const aTitles[],const uint8_t aWidths[])346 void Output::OutputTableHeader(uint8_t aNumColumns, const char *const aTitles[], const uint8_t aWidths[])
347 {
348 for (uint8_t index = 0; index < aNumColumns; index++)
349 {
350 const char *title = aTitles[index];
351 uint8_t width = aWidths[index];
352 size_t titleLength = strlen(title);
353
354 if (titleLength + 2 <= width)
355 {
356 // `title` fits in column width so we write it with extra space
357 // at beginning and end ("| Title |").
358
359 OutputFormat("| %*s", -static_cast<int>(width - 1), title);
360 }
361 else
362 {
363 // Use narrow style (no space at beginning) and write as many
364 // chars from `title` as it can fit in the given column width
365 // ("|Title|").
366
367 OutputFormat("|%*.*s", -static_cast<int>(width), width, title);
368 }
369 }
370
371 OutputLine("|");
372 OutputTableSeparator(aNumColumns, aWidths);
373 }
374
OutputTableSeparator(uint8_t aNumColumns,const uint8_t aWidths[])375 void Output::OutputTableSeparator(uint8_t aNumColumns, const uint8_t aWidths[])
376 {
377 for (uint8_t index = 0; index < aNumColumns; index++)
378 {
379 OutputFormat("+");
380
381 for (uint8_t width = aWidths[index]; width != 0; width--)
382 {
383 OutputFormat("-");
384 }
385 }
386
387 OutputLine("+");
388 }
389
390 } // namespace Cli
391 } // namespace ot
392