/src/openthread/src/cli/cli_utils.cpp
Line | Count | Source |
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_utils.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 Utils::kUnknownString[] = "unknown"; |
52 | | |
53 | | OutputImplementer::OutputImplementer(otCliOutputCallback aCallback, void *aCallbackContext) |
54 | 7.40k | : mCallback(aCallback) |
55 | 7.40k | , mCallbackContext(aCallbackContext) |
56 | | #if OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE |
57 | 7.40k | , mOutputLength(0) |
58 | 7.40k | , mEmittingCommandOutput(true) |
59 | | #endif |
60 | 7.40k | { |
61 | 7.40k | } |
62 | | |
63 | | void Utils::OutputFormat(const char *aFormat, ...) |
64 | 106k | { |
65 | 106k | va_list args; |
66 | | |
67 | 106k | va_start(args, aFormat); |
68 | 106k | OutputFormatV(aFormat, args); |
69 | 106k | va_end(args); |
70 | 106k | } |
71 | | |
72 | | void Utils::OutputFormat(uint8_t aIndentSize, const char *aFormat, ...) |
73 | 538 | { |
74 | 538 | va_list args; |
75 | | |
76 | 538 | OutputSpaces(aIndentSize); |
77 | | |
78 | 538 | va_start(args, aFormat); |
79 | 538 | OutputFormatV(aFormat, args); |
80 | 538 | va_end(args); |
81 | 538 | } |
82 | | |
83 | | void Utils::OutputLine(const char *aFormat, ...) |
84 | 16.2k | { |
85 | 16.2k | va_list args; |
86 | | |
87 | 16.2k | va_start(args, aFormat); |
88 | 16.2k | OutputFormatV(aFormat, args); |
89 | 16.2k | va_end(args); |
90 | | |
91 | 16.2k | OutputNewLine(); |
92 | 16.2k | } |
93 | | |
94 | | void Utils::OutputLine(uint8_t aIndentSize, const char *aFormat, ...) |
95 | 3.04k | { |
96 | 3.04k | va_list args; |
97 | | |
98 | 3.04k | OutputSpaces(aIndentSize); |
99 | | |
100 | 3.04k | va_start(args, aFormat); |
101 | 3.04k | OutputFormatV(aFormat, args); |
102 | 3.04k | va_end(args); |
103 | | |
104 | 3.04k | OutputNewLine(); |
105 | 3.04k | } |
106 | | |
107 | 20.6k | void Utils::OutputNewLine(void) { OutputFormat("\r\n"); } |
108 | | |
109 | 3.57k | void Utils::OutputSpaces(uint8_t aCount) { OutputFormat("%*s", aCount, ""); } |
110 | | |
111 | | void Utils::OutputBytes(const uint8_t *aBytes, uint16_t aLength) |
112 | 241 | { |
113 | 4.59k | for (uint16_t i = 0; i < aLength; i++) |
114 | 4.35k | { |
115 | 4.35k | OutputFormat("%02x", aBytes[i]); |
116 | 4.35k | } |
117 | 241 | } |
118 | | |
119 | | void Utils::OutputBytesLine(const uint8_t *aBytes, uint16_t aLength) |
120 | 156 | { |
121 | 156 | OutputBytes(aBytes, aLength); |
122 | 156 | OutputNewLine(); |
123 | 156 | } |
124 | | |
125 | | const char *Utils::Uint64ToString(uint64_t aUint64, Uint64StringBuffer &aBuffer) |
126 | 1.11k | { |
127 | 1.11k | char *cur = &aBuffer.mChars[Uint64StringBuffer::kSize - 1]; |
128 | | |
129 | 1.11k | *cur = '\0'; |
130 | | |
131 | 1.11k | if (aUint64 == 0) |
132 | 1.03k | { |
133 | 1.03k | *(--cur) = '0'; |
134 | 1.03k | } |
135 | 77 | else |
136 | 77 | { |
137 | 429 | for (; aUint64 != 0; aUint64 /= 10) |
138 | 352 | { |
139 | 352 | *(--cur) = static_cast<char>('0' + static_cast<uint8_t>(aUint64 % 10)); |
140 | 352 | } |
141 | 77 | } |
142 | | |
143 | 1.11k | return cur; |
144 | 1.11k | } |
145 | | |
146 | | void Utils::OutputUint64(uint64_t aUint64) |
147 | 152 | { |
148 | 152 | Uint64StringBuffer buffer; |
149 | | |
150 | 152 | OutputFormat("%s", Uint64ToString(aUint64, buffer)); |
151 | 152 | } |
152 | | |
153 | | void Utils::OutputUint64Line(uint64_t aUint64) |
154 | 152 | { |
155 | 152 | OutputUint64(aUint64); |
156 | 152 | OutputNewLine(); |
157 | 152 | } |
158 | | |
159 | 61 | void Utils::OutputEnabledDisabledStatus(bool aEnabled) { OutputLine(aEnabled ? "Enabled" : "Disabled"); } |
160 | | |
161 | | #if OPENTHREAD_FTD || OPENTHREAD_MTD |
162 | | |
163 | | void Utils::OutputIp6Address(const otIp6Address &aAddress) |
164 | 790 | { |
165 | 790 | char string[OT_IP6_ADDRESS_STRING_SIZE]; |
166 | | |
167 | 790 | otIp6AddressToString(&aAddress, string, sizeof(string)); |
168 | | |
169 | 790 | return OutputFormat("%s", string); |
170 | 790 | } |
171 | | |
172 | | void Utils::OutputIp6AddressLine(const otIp6Address &aAddress) |
173 | 634 | { |
174 | 634 | OutputIp6Address(aAddress); |
175 | 634 | OutputNewLine(); |
176 | 634 | } |
177 | | |
178 | | void Utils::OutputIp6Prefix(const otIp6Prefix &aPrefix) |
179 | 219 | { |
180 | 219 | char string[OT_IP6_PREFIX_STRING_SIZE]; |
181 | | |
182 | 219 | otIp6PrefixToString(&aPrefix, string, sizeof(string)); |
183 | | |
184 | 219 | OutputFormat("%s", string); |
185 | 219 | } |
186 | | |
187 | | void Utils::OutputIp6PrefixLine(const otIp6Prefix &aPrefix) |
188 | 79 | { |
189 | 79 | OutputIp6Prefix(aPrefix); |
190 | 79 | OutputNewLine(); |
191 | 79 | } |
192 | | |
193 | | void Utils::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 Utils::OutputIp6PrefixLine(const otIp6NetworkPrefix &aPrefix) |
200 | 3 | { |
201 | 3 | OutputIp6Prefix(aPrefix); |
202 | 3 | OutputNewLine(); |
203 | 3 | } |
204 | | |
205 | | void Utils::OutputSockAddr(const otSockAddr &aSockAddr) |
206 | 26 | { |
207 | 26 | char string[OT_IP6_SOCK_ADDR_STRING_SIZE]; |
208 | | |
209 | 26 | otIp6SockAddrToString(&aSockAddr, string, sizeof(string)); |
210 | | |
211 | 26 | return OutputFormat("%s", string); |
212 | 26 | } |
213 | | |
214 | | void Utils::OutputSockAddrLine(const otSockAddr &aSockAddr) |
215 | 26 | { |
216 | 26 | OutputSockAddr(aSockAddr); |
217 | 26 | OutputNewLine(); |
218 | 26 | } |
219 | | |
220 | | void Utils::OutputDnsTxtData(const uint8_t *aTxtData, uint16_t aTxtDataLength) |
221 | 0 | { |
222 | 0 | OutputDnsTxtData(/* aKeyValuePerLine */ false, 0, aTxtData, aTxtDataLength); |
223 | 0 | } |
224 | | |
225 | | void Utils::OutputDnsTxtData(uint8_t aIndentSize, const uint8_t *aTxtData, uint16_t aTxtDataLength) |
226 | 0 | { |
227 | 0 | OutputDnsTxtData(/* aKeyValuePerLine */ true, aIndentSize, aTxtData, aTxtDataLength); |
228 | 0 | } |
229 | | |
230 | | void Utils::OutputDnsTxtData(bool aKeyValuePerLine, |
231 | | uint8_t aIndentSize, |
232 | | const uint8_t *aTxtData, |
233 | | uint16_t aTxtDataLength) |
234 | 0 | { |
235 | 0 | otDnsTxtEntry entry; |
236 | 0 | otDnsTxtEntryIterator iterator; |
237 | 0 | bool isFirst = true; |
238 | |
|
239 | 0 | otDnsInitTxtEntryIterator(&iterator, aTxtData, aTxtDataLength); |
240 | |
|
241 | 0 | if (!aKeyValuePerLine) |
242 | 0 | { |
243 | 0 | OutputFormat("["); |
244 | 0 | } |
245 | |
|
246 | 0 | while (otDnsGetNextTxtEntry(&iterator, &entry) == OT_ERROR_NONE) |
247 | 0 | { |
248 | 0 | if (aKeyValuePerLine) |
249 | 0 | { |
250 | 0 | OutputSpaces(aIndentSize); |
251 | 0 | } |
252 | 0 | else if (!isFirst) |
253 | 0 | { |
254 | 0 | OutputFormat(", "); |
255 | 0 | } |
256 | |
|
257 | 0 | if (entry.mKey == nullptr) |
258 | 0 | { |
259 | | // A null `mKey` indicates that the key in the entry is |
260 | | // longer than the recommended max key length, so the entry |
261 | | // could not be parsed. In this case, the whole entry is |
262 | | // returned encoded in `mValue`. |
263 | |
|
264 | 0 | OutputFormat("["); |
265 | 0 | OutputBytes(entry.mValue, entry.mValueLength); |
266 | 0 | OutputFormat("]"); |
267 | 0 | } |
268 | 0 | else |
269 | 0 | { |
270 | 0 | OutputFormat("%s", entry.mKey); |
271 | |
|
272 | 0 | if (entry.mValue != nullptr) |
273 | 0 | { |
274 | 0 | OutputFormat("="); |
275 | 0 | OutputBytes(entry.mValue, entry.mValueLength); |
276 | 0 | } |
277 | 0 | } |
278 | |
|
279 | 0 | isFirst = false; |
280 | |
|
281 | 0 | if (aKeyValuePerLine) |
282 | 0 | { |
283 | 0 | OutputNewLine(); |
284 | 0 | } |
285 | 0 | } |
286 | |
|
287 | 0 | if (!aKeyValuePerLine) |
288 | 0 | { |
289 | 0 | OutputFormat("]"); |
290 | 0 | } |
291 | 0 | } |
292 | | |
293 | | const char *Utils::PercentageToString(uint16_t aValue, PercentageStringBuffer &aBuffer) |
294 | 0 | { |
295 | 0 | uint32_t scaledValue = aValue; |
296 | 0 | StringWriter writer(aBuffer.mChars, sizeof(aBuffer.mChars)); |
297 | |
|
298 | 0 | scaledValue = (scaledValue * 10000) / 0xffff; |
299 | 0 | writer.Append("%u.%02u", static_cast<uint16_t>(scaledValue / 100), static_cast<uint16_t>(scaledValue % 100)); |
300 | |
|
301 | 0 | return aBuffer.mChars; |
302 | 0 | } |
303 | | |
304 | | #endif // OPENTHREAD_FTD || OPENTHREAD_MTD |
305 | | |
306 | 126k | void Utils::OutputFormatV(const char *aFormat, va_list aArguments) { mImplementer.OutputV(aFormat, aArguments); } |
307 | | |
308 | | void OutputImplementer::OutputV(const char *aFormat, va_list aArguments) |
309 | 126k | { |
310 | 126k | #if OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE |
311 | 126k | va_list args; |
312 | 126k | int charsWritten; |
313 | 126k | bool truncated = false; |
314 | | |
315 | 126k | va_copy(args, aArguments); |
316 | 126k | #endif |
317 | | |
318 | 126k | mCallback(mCallbackContext, aFormat, aArguments); |
319 | | |
320 | 126k | #if OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE |
321 | 126k | VerifyOrExit(mEmittingCommandOutput); |
322 | | |
323 | 111k | charsWritten = vsnprintf(&mOutputString[mOutputLength], sizeof(mOutputString) - mOutputLength, aFormat, args); |
324 | | |
325 | 111k | VerifyOrExit(charsWritten >= 0, mOutputLength = 0); |
326 | | |
327 | 111k | if (static_cast<uint32_t>(charsWritten) >= sizeof(mOutputString) - mOutputLength) |
328 | 0 | { |
329 | 0 | truncated = true; |
330 | 0 | mOutputLength = sizeof(mOutputString) - 1; |
331 | 0 | } |
332 | 111k | else |
333 | 111k | { |
334 | 111k | mOutputLength += charsWritten; |
335 | 111k | } |
336 | | |
337 | 132k | while (true) |
338 | 132k | { |
339 | 132k | char *lineEnd = strchr(mOutputString, '\r'); |
340 | | |
341 | 132k | if (lineEnd == nullptr) |
342 | 111k | { |
343 | 111k | break; |
344 | 111k | } |
345 | | |
346 | 20.6k | *lineEnd = '\0'; |
347 | | |
348 | 20.6k | if (lineEnd > mOutputString) |
349 | 20.5k | { |
350 | 20.5k | otLogCli(OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_LEVEL, "Output: %s", mOutputString); |
351 | 20.5k | } |
352 | | |
353 | 20.6k | lineEnd++; |
354 | | |
355 | 41.4k | while ((*lineEnd == '\n') || (*lineEnd == '\r')) |
356 | 20.7k | { |
357 | 20.7k | lineEnd++; |
358 | 20.7k | } |
359 | | |
360 | | // Example of the pointers and lengths. |
361 | | // |
362 | | // - mOutputString = "hi\r\nmore" |
363 | | // - mOutputLength = 8 |
364 | | // - lineEnd = &mOutputString[4] |
365 | | // |
366 | | // |
367 | | // 0 1 2 3 4 5 6 7 8 9 |
368 | | // +----+----+----+----+----+----+----+----+----+--- |
369 | | // | h | i | \r | \n | m | o | r | e | \0 | |
370 | | // +----+----+----+----+----+----+----+----+----+--- |
371 | | // ^ ^ |
372 | | // | | |
373 | | // lineEnd mOutputString[mOutputLength] |
374 | | // |
375 | | // |
376 | | // New length is `&mOutputString[8] - &mOutputString[4] -> 4`. |
377 | | // |
378 | | // We move (newLen + 1 = 5) chars from `lineEnd` to start of |
379 | | // `mOutputString` which will include the `\0` char. |
380 | | // |
381 | | // If `lineEnd` and `mOutputString[mOutputLength]` are the same |
382 | | // the code works correctly as well (new length set to zero and |
383 | | // the `\0` is copied). |
384 | | |
385 | 20.6k | mOutputLength = static_cast<uint16_t>(&mOutputString[mOutputLength] - lineEnd); |
386 | 20.6k | memmove(mOutputString, lineEnd, mOutputLength + 1); |
387 | 20.6k | } |
388 | | |
389 | 111k | if (truncated) |
390 | 0 | { |
391 | 0 | otLogCli(OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_LEVEL, "Output: %s ...", mOutputString); |
392 | 0 | mOutputLength = 0; |
393 | 0 | } |
394 | | |
395 | 126k | exit: |
396 | 126k | va_end(args); |
397 | 126k | #endif // OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE |
398 | 126k | } |
399 | | |
400 | | #if OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE |
401 | | void Utils::LogInput(const Arg *aArgs) |
402 | 6.98k | { |
403 | 6.98k | String<kInputOutputLogStringSize> inputString; |
404 | | |
405 | 35.2k | for (bool isFirst = true; !aArgs->IsEmpty(); aArgs++, isFirst = false) |
406 | 28.3k | { |
407 | 28.3k | inputString.Append(isFirst ? "%s" : " %s", aArgs->GetCString()); |
408 | 28.3k | } |
409 | | |
410 | 6.98k | otLogCli(OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_LEVEL, "Input: %s", inputString.AsCString()); |
411 | 6.98k | } |
412 | | #endif |
413 | | |
414 | | void Utils::OutputTableHeader(uint8_t aNumColumns, const char *const aTitles[], const uint8_t aWidths[]) |
415 | 694 | { |
416 | 4.56k | for (uint8_t index = 0; index < aNumColumns; index++) |
417 | 3.87k | { |
418 | 3.87k | const char *title = aTitles[index]; |
419 | 3.87k | uint8_t width = aWidths[index]; |
420 | 3.87k | size_t titleLength = strlen(title); |
421 | | |
422 | 3.87k | if (titleLength + 2 <= width) |
423 | 3.49k | { |
424 | | // `title` fits in column width so we write it with extra space |
425 | | // at beginning and end ("| Title |"). |
426 | | |
427 | 3.49k | OutputFormat("| %*s", -static_cast<int>(width - 1), title); |
428 | 3.49k | } |
429 | 378 | else |
430 | 378 | { |
431 | | // Use narrow style (no space at beginning) and write as many |
432 | | // chars from `title` as it can fit in the given column width |
433 | | // ("|Title|"). |
434 | | |
435 | 378 | OutputFormat("|%*.*s", -static_cast<int>(width), width, title); |
436 | 378 | } |
437 | 3.87k | } |
438 | | |
439 | 694 | OutputLine("|"); |
440 | 694 | OutputTableSeparator(aNumColumns, aWidths); |
441 | 694 | } |
442 | | |
443 | | void Utils::OutputTableSeparator(uint8_t aNumColumns, const uint8_t aWidths[]) |
444 | 694 | { |
445 | 4.56k | for (uint8_t index = 0; index < aNumColumns; index++) |
446 | 3.87k | { |
447 | 3.87k | OutputFormat("+"); |
448 | | |
449 | 55.7k | for (uint8_t width = aWidths[index]; width != 0; width--) |
450 | 51.8k | { |
451 | 51.8k | OutputFormat("-"); |
452 | 51.8k | } |
453 | 3.87k | } |
454 | | |
455 | 694 | OutputLine("+"); |
456 | 694 | } |
457 | | |
458 | | otError Utils::ParseEnableOrDisable(const Arg &aArg, bool &aEnable) |
459 | 296 | { |
460 | 296 | otError error = OT_ERROR_NONE; |
461 | | |
462 | 296 | if (aArg == "enable") |
463 | 9 | { |
464 | 9 | aEnable = true; |
465 | 9 | } |
466 | 287 | else if (aArg == "disable") |
467 | 8 | { |
468 | 8 | aEnable = false; |
469 | 8 | } |
470 | 279 | else |
471 | 279 | { |
472 | 279 | error = OT_ERROR_INVALID_COMMAND; |
473 | 279 | } |
474 | | |
475 | 296 | return error; |
476 | 296 | } |
477 | | |
478 | | otError Utils::ProcessEnableDisable(Arg aArgs[], SetEnabledHandler aSetEnabledHandler) |
479 | 291 | { |
480 | 291 | otError error = OT_ERROR_NONE; |
481 | 291 | bool enable; |
482 | | |
483 | 291 | if (ParseEnableOrDisable(aArgs[0], enable) == OT_ERROR_NONE) |
484 | 16 | { |
485 | 16 | aSetEnabledHandler(GetInstancePtr(), enable); |
486 | 16 | } |
487 | 275 | else |
488 | 275 | { |
489 | 275 | error = OT_ERROR_INVALID_COMMAND; |
490 | 275 | } |
491 | | |
492 | 291 | return error; |
493 | 291 | } |
494 | | |
495 | | otError Utils::ProcessEnableDisable(Arg aArgs[], SetEnabledHandlerFailable aSetEnabledHandler) |
496 | 1 | { |
497 | 1 | otError error = OT_ERROR_NONE; |
498 | 1 | bool enable; |
499 | | |
500 | 1 | if (ParseEnableOrDisable(aArgs[0], enable) == OT_ERROR_NONE) |
501 | 0 | { |
502 | 0 | error = aSetEnabledHandler(GetInstancePtr(), enable); |
503 | 0 | } |
504 | 1 | else |
505 | 1 | { |
506 | 1 | error = OT_ERROR_INVALID_COMMAND; |
507 | 1 | } |
508 | | |
509 | 1 | return error; |
510 | 1 | } |
511 | | |
512 | | otError Utils::ProcessEnableDisable(Arg aArgs[], |
513 | | IsEnabledHandler aIsEnabledHandler, |
514 | | SetEnabledHandler aSetEnabledHandler) |
515 | 99 | { |
516 | 99 | otError error = OT_ERROR_NONE; |
517 | | |
518 | 99 | if (aArgs[0].IsEmpty()) |
519 | 30 | { |
520 | 30 | OutputEnabledDisabledStatus(aIsEnabledHandler(GetInstancePtr())); |
521 | 30 | } |
522 | 69 | else |
523 | 69 | { |
524 | 69 | error = ProcessEnableDisable(aArgs, aSetEnabledHandler); |
525 | 69 | } |
526 | | |
527 | 99 | return error; |
528 | 99 | } |
529 | | |
530 | | otError Utils::ProcessEnableDisable(Arg aArgs[], |
531 | | IsEnabledHandler aIsEnabledHandler, |
532 | | SetEnabledHandlerFailable aSetEnabledHandler) |
533 | 2 | { |
534 | 2 | otError error = OT_ERROR_NONE; |
535 | | |
536 | 2 | if (aArgs[0].IsEmpty()) |
537 | 1 | { |
538 | 1 | OutputEnabledDisabledStatus(aIsEnabledHandler(GetInstancePtr())); |
539 | 1 | } |
540 | 1 | else |
541 | 1 | { |
542 | 1 | error = ProcessEnableDisable(aArgs, aSetEnabledHandler); |
543 | 1 | } |
544 | | |
545 | 2 | return error; |
546 | 2 | } |
547 | | |
548 | | otError Utils::ParseJoinerDiscerner(Arg &aArg, otJoinerDiscerner &aDiscerner) |
549 | 11 | { |
550 | 11 | otError error; |
551 | 11 | char *separator; |
552 | | |
553 | 11 | VerifyOrExit(!aArg.IsEmpty(), error = OT_ERROR_INVALID_ARGS); |
554 | | |
555 | 11 | separator = strstr(aArg.GetCString(), "/"); |
556 | | |
557 | 11 | VerifyOrExit(separator != nullptr, error = OT_ERROR_NOT_FOUND); |
558 | | |
559 | 9 | SuccessOrExit(error = ot::Utils::CmdLineParser::ParseAsUint8(separator + 1, aDiscerner.mLength)); |
560 | 7 | VerifyOrExit(aDiscerner.mLength > 0 && aDiscerner.mLength <= 64, error = OT_ERROR_INVALID_ARGS); |
561 | 5 | *separator = '\0'; |
562 | 5 | error = aArg.ParseAsUint64(aDiscerner.mValue); |
563 | | |
564 | 11 | exit: |
565 | 11 | return error; |
566 | 5 | } |
567 | | |
568 | | otError Utils::ParsePreference(const Arg &aArg, otRoutePreference &aPreference) |
569 | 236 | { |
570 | 236 | otError error = OT_ERROR_NONE; |
571 | | |
572 | 236 | if (aArg == "high") |
573 | 0 | { |
574 | 0 | aPreference = OT_ROUTE_PREFERENCE_HIGH; |
575 | 0 | } |
576 | 236 | else if (aArg == "med") |
577 | 1 | { |
578 | 1 | aPreference = OT_ROUTE_PREFERENCE_MED; |
579 | 1 | } |
580 | 235 | else if (aArg == "low") |
581 | 1 | { |
582 | 1 | aPreference = OT_ROUTE_PREFERENCE_LOW; |
583 | 1 | } |
584 | 234 | else |
585 | 234 | { |
586 | 234 | error = OT_ERROR_INVALID_ARGS; |
587 | 234 | } |
588 | | |
589 | 236 | return error; |
590 | 236 | } |
591 | | |
592 | | const char *Utils::PreferenceToString(signed int aPreference) |
593 | 241 | { |
594 | 241 | const char *str = ""; |
595 | | |
596 | 241 | switch (aPreference) |
597 | 241 | { |
598 | 171 | case OT_ROUTE_PREFERENCE_LOW: |
599 | 171 | str = "low"; |
600 | 171 | break; |
601 | | |
602 | 70 | case OT_ROUTE_PREFERENCE_MED: |
603 | 70 | str = "med"; |
604 | 70 | break; |
605 | | |
606 | 0 | case OT_ROUTE_PREFERENCE_HIGH: |
607 | 0 | str = "high"; |
608 | 0 | break; |
609 | | |
610 | 0 | default: |
611 | 0 | break; |
612 | 241 | } |
613 | | |
614 | 241 | return str; |
615 | 241 | } |
616 | | |
617 | | #if OPENTHREAD_FTD || OPENTHREAD_MTD |
618 | | otError Utils::ParseToIp6Address(otInstance *aInstance, const Arg &aArg, otIp6Address &aAddress, bool &aSynthesized) |
619 | 1.32k | { |
620 | 1.32k | Error error = OT_ERROR_NONE; |
621 | | |
622 | 1.32k | VerifyOrExit(!aArg.IsEmpty(), error = OT_ERROR_INVALID_ARGS); |
623 | 1.32k | error = aArg.ParseAsIp6Address(aAddress); |
624 | 1.32k | aSynthesized = false; |
625 | | |
626 | 1.32k | if (error != OT_ERROR_NONE) |
627 | 25 | { |
628 | | // It might be an IPv4 address, let's have a try. |
629 | 25 | otIp4Address ip4Address; |
630 | | |
631 | | // Do not touch the error value if we failed to parse it as an IPv4 address. |
632 | 25 | SuccessOrExit(aArg.ParseAsIp4Address(ip4Address)); |
633 | 8 | SuccessOrExit(error = otNat64SynthesizeIp6Address(aInstance, &ip4Address, &aAddress)); |
634 | 8 | aSynthesized = true; |
635 | 8 | } |
636 | | |
637 | 1.32k | exit: |
638 | 1.32k | return error; |
639 | 1.32k | } |
640 | | |
641 | | #if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE |
642 | | otError Utils::ParsePrefix(Arg aArgs[], otBorderRouterConfig &aConfig) |
643 | 101 | { |
644 | 101 | otError error = OT_ERROR_NONE; |
645 | | |
646 | 101 | ClearAllBytes(aConfig); |
647 | | |
648 | 101 | SuccessOrExit(error = aArgs[0].ParseAsIp6Prefix(aConfig.mPrefix)); |
649 | 97 | aArgs++; |
650 | | |
651 | 228 | for (; !aArgs->IsEmpty(); aArgs++) |
652 | 138 | { |
653 | 138 | otRoutePreference preference; |
654 | | |
655 | 138 | if (ParsePreference(*aArgs, preference) == OT_ERROR_NONE) |
656 | 0 | { |
657 | 0 | aConfig.mPreference = preference; |
658 | 0 | } |
659 | 138 | else |
660 | 138 | { |
661 | 2.17k | for (char *arg = aArgs->GetCString(); *arg != '\0'; arg++) |
662 | 2.03k | { |
663 | 2.03k | switch (*arg) |
664 | 2.03k | { |
665 | 204 | case 'p': |
666 | 204 | aConfig.mPreferred = true; |
667 | 204 | break; |
668 | | |
669 | 207 | case 'a': |
670 | 207 | aConfig.mSlaac = true; |
671 | 207 | break; |
672 | | |
673 | 200 | case 'd': |
674 | 200 | aConfig.mDhcp = true; |
675 | 200 | break; |
676 | | |
677 | 198 | case 'c': |
678 | 198 | aConfig.mConfigure = true; |
679 | 198 | break; |
680 | | |
681 | 204 | case 'r': |
682 | 204 | aConfig.mDefaultRoute = true; |
683 | 204 | break; |
684 | | |
685 | 198 | case 'o': |
686 | 198 | aConfig.mOnMesh = true; |
687 | 198 | break; |
688 | | |
689 | 195 | case 's': |
690 | 195 | aConfig.mStable = true; |
691 | 195 | break; |
692 | | |
693 | 206 | case 'n': |
694 | 206 | aConfig.mNdDns = true; |
695 | 206 | break; |
696 | | |
697 | 0 | #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE |
698 | 201 | case 'D': |
699 | 201 | aConfig.mDp = true; |
700 | 201 | break; |
701 | 0 | #endif |
702 | 219 | case '-': |
703 | 219 | break; |
704 | | |
705 | 7 | default: |
706 | 7 | ExitNow(error = OT_ERROR_INVALID_ARGS); |
707 | 2.03k | } |
708 | 2.03k | } |
709 | 138 | } |
710 | 138 | } |
711 | | |
712 | 101 | exit: |
713 | 101 | return error; |
714 | 97 | } |
715 | | |
716 | | otError Utils::ParseRoute(Arg aArgs[], otExternalRouteConfig &aConfig) |
717 | 94 | { |
718 | 94 | otError error = OT_ERROR_NONE; |
719 | | |
720 | 94 | ClearAllBytes(aConfig); |
721 | | |
722 | 94 | SuccessOrExit(error = aArgs[0].ParseAsIp6Prefix(aConfig.mPrefix)); |
723 | 71 | aArgs++; |
724 | | |
725 | 156 | for (; !aArgs->IsEmpty(); aArgs++) |
726 | 94 | { |
727 | 94 | otRoutePreference preference; |
728 | | |
729 | 94 | if (ParsePreference(*aArgs, preference) == OT_ERROR_NONE) |
730 | 0 | { |
731 | 0 | aConfig.mPreference = preference; |
732 | 0 | } |
733 | 94 | else |
734 | 94 | { |
735 | 987 | for (char *arg = aArgs->GetCString(); *arg != '\0'; arg++) |
736 | 902 | { |
737 | 902 | switch (*arg) |
738 | 902 | { |
739 | 202 | case 's': |
740 | 202 | aConfig.mStable = true; |
741 | 202 | break; |
742 | | |
743 | 248 | case 'n': |
744 | 248 | aConfig.mNat64 = true; |
745 | 248 | break; |
746 | | |
747 | 245 | case 'a': |
748 | 245 | aConfig.mAdvPio = true; |
749 | 245 | break; |
750 | | |
751 | 198 | case '-': |
752 | 198 | break; |
753 | | |
754 | 9 | default: |
755 | 9 | ExitNow(error = OT_ERROR_INVALID_ARGS); |
756 | 902 | } |
757 | 902 | } |
758 | 94 | } |
759 | 94 | } |
760 | | |
761 | 94 | exit: |
762 | 94 | return error; |
763 | 71 | } |
764 | | #endif // OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE |
765 | | #endif // #if OPENTHREAD_FTD || OPENTHREAD_MTD |
766 | | |
767 | | const char *Utils::LinkModeToString(const otLinkModeConfig &aLinkMode, char (&aStringBuffer)[kLinkModeStringSize]) |
768 | 84 | { |
769 | 84 | char *flagsPtr = &aStringBuffer[0]; |
770 | | |
771 | 84 | if (aLinkMode.mRxOnWhenIdle) |
772 | 84 | { |
773 | 84 | *flagsPtr++ = 'r'; |
774 | 84 | } |
775 | | |
776 | 84 | if (aLinkMode.mDeviceType) |
777 | 84 | { |
778 | 84 | *flagsPtr++ = 'd'; |
779 | 84 | } |
780 | | |
781 | 84 | if (aLinkMode.mNetworkData) |
782 | 84 | { |
783 | 84 | *flagsPtr++ = 'n'; |
784 | 84 | } |
785 | | |
786 | 84 | if (flagsPtr == &aStringBuffer[0]) |
787 | 0 | { |
788 | 0 | *flagsPtr++ = '-'; |
789 | 0 | } |
790 | | |
791 | 84 | *flagsPtr = '\0'; |
792 | | |
793 | 84 | return aStringBuffer; |
794 | 84 | } |
795 | | |
796 | | const char *Utils::AddressOriginToString(uint8_t aOrigin) |
797 | 303 | { |
798 | 303 | static const char *const kOriginStrings[4] = { |
799 | 303 | "thread", // 0, OT_ADDRESS_ORIGIN_THREAD |
800 | 303 | "slaac", // 1, OT_ADDRESS_ORIGIN_SLAAC |
801 | 303 | "dhcp6", // 2, OT_ADDRESS_ORIGIN_DHCPV6 |
802 | 303 | "manual", // 3, OT_ADDRESS_ORIGIN_MANUAL |
803 | 303 | }; |
804 | | |
805 | 303 | static_assert(0 == OT_ADDRESS_ORIGIN_THREAD, "OT_ADDRESS_ORIGIN_THREAD value is incorrect"); |
806 | 303 | static_assert(1 == OT_ADDRESS_ORIGIN_SLAAC, "OT_ADDRESS_ORIGIN_SLAAC value is incorrect"); |
807 | 303 | static_assert(2 == OT_ADDRESS_ORIGIN_DHCPV6, "OT_ADDRESS_ORIGIN_DHCPV6 value is incorrect"); |
808 | 303 | static_assert(3 == OT_ADDRESS_ORIGIN_MANUAL, "OT_ADDRESS_ORIGIN_MANUAL value is incorrect"); |
809 | | |
810 | 303 | return Stringify(aOrigin, kOriginStrings); |
811 | 303 | } |
812 | | |
813 | | const char *Utils::BorderRoutingStateToString(otBorderRoutingState aState) |
814 | 25 | { |
815 | 25 | static const char *const kStateStrings[] = { |
816 | 25 | "uninitialized", // (0) OT_BORDER_ROUTING_STATE_UNINITIALIZED |
817 | 25 | "disabled", // (1) OT_BORDER_ROUTING_STATE_DISABLED |
818 | 25 | "stopped", // (2) OT_BORDER_ROUTING_STATE_STOPPED |
819 | 25 | "running", // (3) OT_BORDER_ROUTING_STATE_RUNNING |
820 | 25 | }; |
821 | | |
822 | 25 | static_assert(0 == OT_BORDER_ROUTING_STATE_UNINITIALIZED, "STATE_UNINITIALIZED value is incorrect"); |
823 | 25 | static_assert(1 == OT_BORDER_ROUTING_STATE_DISABLED, "STATE_DISABLED value is incorrect"); |
824 | 25 | static_assert(2 == OT_BORDER_ROUTING_STATE_STOPPED, "STATE_STOPPED value is incorrect"); |
825 | 25 | static_assert(3 == OT_BORDER_ROUTING_STATE_RUNNING, "STATE_RUNNING value is incorrect"); |
826 | | |
827 | 25 | return Stringify(aState, kStateStrings); |
828 | 25 | } |
829 | | |
830 | | } // namespace Cli |
831 | | } // namespace ot |