/src/openthread/src/cli/cli_output.cpp
Line | Count | Source (jump to first uncovered line) |
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 | | |
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 | 8.36k | { |
61 | 8.36k | } |
62 | | |
63 | | void Output::OutputFormat(const char *aFormat, ...) |
64 | 95.9k | { |
65 | 95.9k | va_list args; |
66 | | |
67 | 95.9k | va_start(args, aFormat); |
68 | 95.9k | OutputFormatV(aFormat, args); |
69 | 95.9k | va_end(args); |
70 | 95.9k | } |
71 | | |
72 | | void Output::OutputFormat(uint8_t aIndentSize, const char *aFormat, ...) |
73 | 9 | { |
74 | 9 | va_list args; |
75 | | |
76 | 9 | OutputSpaces(aIndentSize); |
77 | | |
78 | 9 | va_start(args, aFormat); |
79 | 9 | OutputFormatV(aFormat, args); |
80 | 9 | va_end(args); |
81 | 9 | } |
82 | | |
83 | | void Output::OutputLine(const char *aFormat, ...) |
84 | 10.0k | { |
85 | 10.0k | va_list args; |
86 | | |
87 | 10.0k | va_start(args, aFormat); |
88 | 10.0k | OutputFormatV(aFormat, args); |
89 | 10.0k | va_end(args); |
90 | | |
91 | 10.0k | OutputNewLine(); |
92 | 10.0k | } |
93 | | |
94 | | void Output::OutputLine(uint8_t aIndentSize, const char *aFormat, ...) |
95 | 32 | { |
96 | 32 | va_list args; |
97 | | |
98 | 32 | OutputSpaces(aIndentSize); |
99 | | |
100 | 32 | va_start(args, aFormat); |
101 | 32 | OutputFormatV(aFormat, args); |
102 | 32 | va_end(args); |
103 | | |
104 | 32 | OutputNewLine(); |
105 | 32 | } |
106 | | |
107 | 13.3k | void Output::OutputNewLine(void) { OutputFormat("\r\n"); } |
108 | | |
109 | 41 | void Output::OutputSpaces(uint8_t aCount) { OutputFormat("%*s", aCount, ""); } |
110 | | |
111 | | void Output::OutputBytes(const uint8_t *aBytes, uint16_t aLength) |
112 | 3.16k | { |
113 | 53.3k | for (uint16_t i = 0; i < aLength; i++) |
114 | 50.1k | { |
115 | 50.1k | OutputFormat("%02x", aBytes[i]); |
116 | 50.1k | } |
117 | 3.16k | } |
118 | | |
119 | | void Output::OutputBytesLine(const uint8_t *aBytes, uint16_t aLength) |
120 | 3.16k | { |
121 | 3.16k | OutputBytes(aBytes, aLength); |
122 | 3.16k | OutputNewLine(); |
123 | 3.16k | } |
124 | | |
125 | | const char *Output::Uint64ToString(uint64_t aUint64, Uint64StringBuffer &aBuffer) |
126 | 9 | { |
127 | 9 | char *cur = &aBuffer.mChars[Uint64StringBuffer::kSize - 1]; |
128 | | |
129 | 9 | *cur = '\0'; |
130 | | |
131 | 9 | if (aUint64 == 0) |
132 | 4 | { |
133 | 4 | *(--cur) = '0'; |
134 | 4 | } |
135 | 5 | else |
136 | 5 | { |
137 | 22 | for (; aUint64 != 0; aUint64 /= 10) |
138 | 17 | { |
139 | 17 | *(--cur) = static_cast<char>('0' + static_cast<uint8_t>(aUint64 % 10)); |
140 | 17 | } |
141 | 5 | } |
142 | | |
143 | 9 | return cur; |
144 | 9 | } |
145 | | |
146 | | void Output::OutputUint64(uint64_t aUint64) |
147 | 9 | { |
148 | 9 | Uint64StringBuffer buffer; |
149 | | |
150 | 9 | OutputFormat("%s", Uint64ToString(aUint64, buffer)); |
151 | 9 | } |
152 | | |
153 | | void Output::OutputUint64Line(uint64_t aUint64) |
154 | 9 | { |
155 | 9 | OutputUint64(aUint64); |
156 | 9 | OutputNewLine(); |
157 | 9 | } |
158 | | |
159 | 16 | void Output::OutputEnabledDisabledStatus(bool aEnabled) { OutputLine(aEnabled ? "Enabled" : "Disabled"); } |
160 | | |
161 | | #if OPENTHREAD_FTD || OPENTHREAD_MTD |
162 | | |
163 | | void Output::OutputIp6Address(const otIp6Address &aAddress) |
164 | 14 | { |
165 | 14 | char string[OT_IP6_ADDRESS_STRING_SIZE]; |
166 | | |
167 | 14 | otIp6AddressToString(&aAddress, string, sizeof(string)); |
168 | | |
169 | 14 | return OutputFormat("%s", string); |
170 | 14 | } |
171 | | |
172 | | void Output::OutputIp6AddressLine(const otIp6Address &aAddress) |
173 | 10 | { |
174 | 10 | OutputIp6Address(aAddress); |
175 | 10 | OutputNewLine(); |
176 | 10 | } |
177 | | |
178 | | void Output::OutputIp6Prefix(const otIp6Prefix &aPrefix) |
179 | 0 | { |
180 | 0 | char string[OT_IP6_PREFIX_STRING_SIZE]; |
181 | |
|
182 | 0 | otIp6PrefixToString(&aPrefix, string, sizeof(string)); |
183 | |
|
184 | 0 | OutputFormat("%s", string); |
185 | 0 | } |
186 | | |
187 | | void Output::OutputIp6PrefixLine(const otIp6Prefix &aPrefix) |
188 | 0 | { |
189 | 0 | OutputIp6Prefix(aPrefix); |
190 | 0 | OutputNewLine(); |
191 | 0 | } |
192 | | |
193 | | void Output::OutputIp6Prefix(const otIp6NetworkPrefix &aPrefix) |
194 | 3 | { |
195 | 3 | OutputFormat("%x:%x:%x:%x::/64", (aPrefix.m8[0] << 8) | aPrefix.m8[1], (aPrefix.m8[2] << 8) | aPrefix.m8[3], |
196 | 3 | (aPrefix.m8[4] << 8) | aPrefix.m8[5], (aPrefix.m8[6] << 8) | aPrefix.m8[7]); |
197 | 3 | } |
198 | | |
199 | | void Output::OutputIp6PrefixLine(const otIp6NetworkPrefix &aPrefix) |
200 | 3 | { |
201 | 3 | OutputIp6Prefix(aPrefix); |
202 | 3 | OutputNewLine(); |
203 | 3 | } |
204 | | |
205 | | void Output::OutputSockAddr(const otSockAddr &aSockAddr) |
206 | 10 | { |
207 | 10 | char string[OT_IP6_SOCK_ADDR_STRING_SIZE]; |
208 | | |
209 | 10 | otIp6SockAddrToString(&aSockAddr, string, sizeof(string)); |
210 | | |
211 | 10 | return OutputFormat("%s", string); |
212 | 10 | } |
213 | | |
214 | | void Output::OutputSockAddrLine(const otSockAddr &aSockAddr) |
215 | 10 | { |
216 | 10 | OutputSockAddr(aSockAddr); |
217 | 10 | OutputNewLine(); |
218 | 10 | } |
219 | | |
220 | | void Output::OutputDnsTxtData(const uint8_t *aTxtData, uint16_t aTxtDataLength) |
221 | 0 | { |
222 | 0 | otDnsTxtEntry entry; |
223 | 0 | otDnsTxtEntryIterator iterator; |
224 | 0 | bool isFirst = true; |
225 | |
|
226 | 0 | otDnsInitTxtEntryIterator(&iterator, aTxtData, aTxtDataLength); |
227 | |
|
228 | 0 | OutputFormat("["); |
229 | |
|
230 | 0 | while (otDnsGetNextTxtEntry(&iterator, &entry) == OT_ERROR_NONE) |
231 | 0 | { |
232 | 0 | if (!isFirst) |
233 | 0 | { |
234 | 0 | OutputFormat(", "); |
235 | 0 | } |
236 | |
|
237 | 0 | if (entry.mKey == nullptr) |
238 | 0 | { |
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 | 0 | OutputFormat("["); |
245 | 0 | OutputBytes(entry.mValue, entry.mValueLength); |
246 | 0 | OutputFormat("]"); |
247 | 0 | } |
248 | 0 | else |
249 | 0 | { |
250 | 0 | OutputFormat("%s", entry.mKey); |
251 | |
|
252 | 0 | if (entry.mValue != nullptr) |
253 | 0 | { |
254 | 0 | OutputFormat("="); |
255 | 0 | OutputBytes(entry.mValue, entry.mValueLength); |
256 | 0 | } |
257 | 0 | } |
258 | |
|
259 | 0 | isFirst = false; |
260 | 0 | } |
261 | |
|
262 | 0 | OutputFormat("]"); |
263 | 0 | } |
264 | | |
265 | | const char *Output::PercentageToString(uint16_t aValue, PercentageStringBuffer &aBuffer) |
266 | 16 | { |
267 | 16 | uint32_t scaledValue = aValue; |
268 | 16 | StringWriter writer(aBuffer.mChars, sizeof(aBuffer.mChars)); |
269 | | |
270 | 16 | scaledValue = (scaledValue * 10000) / 0xffff; |
271 | 16 | writer.Append("%u.%02u", static_cast<uint16_t>(scaledValue / 100), static_cast<uint16_t>(scaledValue % 100)); |
272 | | |
273 | 16 | return aBuffer.mChars; |
274 | 16 | } |
275 | | |
276 | | #endif // OPENTHREAD_FTD || OPENTHREAD_MTD |
277 | | |
278 | 106k | void Output::OutputFormatV(const char *aFormat, va_list aArguments) { mImplementer.OutputV(aFormat, aArguments); } |
279 | | |
280 | | void OutputImplementer::OutputV(const char *aFormat, va_list aArguments) |
281 | 106k | { |
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 | 106k | 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 | 106k | } |
371 | | |
372 | | #if OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE |
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 | | |
386 | | void Output::OutputTableHeader(uint8_t aNumColumns, const char *const aTitles[], const uint8_t aWidths[]) |
387 | 186 | { |
388 | 1.38k | for (uint8_t index = 0; index < aNumColumns; index++) |
389 | 1.19k | { |
390 | 1.19k | const char *title = aTitles[index]; |
391 | 1.19k | uint8_t width = aWidths[index]; |
392 | 1.19k | size_t titleLength = strlen(title); |
393 | | |
394 | 1.19k | if (titleLength + 2 <= width) |
395 | 1.07k | { |
396 | | // `title` fits in column width so we write it with extra space |
397 | | // at beginning and end ("| Title |"). |
398 | | |
399 | 1.07k | OutputFormat("| %*s", -static_cast<int>(width - 1), title); |
400 | 1.07k | } |
401 | 119 | else |
402 | 119 | { |
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 | 119 | OutputFormat("|%*.*s", -static_cast<int>(width), width, title); |
408 | 119 | } |
409 | 1.19k | } |
410 | | |
411 | 186 | OutputLine("|"); |
412 | 186 | OutputTableSeparator(aNumColumns, aWidths); |
413 | 186 | } |
414 | | |
415 | | void Output::OutputTableSeparator(uint8_t aNumColumns, const uint8_t aWidths[]) |
416 | 186 | { |
417 | 1.38k | for (uint8_t index = 0; index < aNumColumns; index++) |
418 | 1.19k | { |
419 | 1.19k | OutputFormat("+"); |
420 | | |
421 | 14.6k | for (uint8_t width = aWidths[index]; width != 0; width--) |
422 | 13.4k | { |
423 | 13.4k | OutputFormat("-"); |
424 | 13.4k | } |
425 | 1.19k | } |
426 | | |
427 | 186 | OutputLine("+"); |
428 | 186 | } |
429 | | |
430 | | } // namespace Cli |
431 | | } // namespace ot |