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 implements CLI for the History Tracker.
32 */
33
34 #include "cli_history.hpp"
35
36 #if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
37
38 #include <string.h>
39
40 #include "cli/cli.hpp"
41
42 namespace ot {
43 namespace Cli {
44
45 static const char *const kSimpleEventStrings[] = {
46 "Added", // (0) OT_HISTORY_TRACKER_{NET_DATA_ENTRY/ADDRESSS_EVENT}_ADDED
47 "Removed" // (1) OT_HISTORY_TRACKER_{NET_DATA_ENTRY/ADDRESS_EVENT}_REMOVED
48 };
49
ParseArgs(Arg aArgs[],bool & aIsList,uint16_t & aNumEntries) const50 otError History::ParseArgs(Arg aArgs[], bool &aIsList, uint16_t &aNumEntries) const
51 {
52 if (*aArgs == "list")
53 {
54 aArgs++;
55 aIsList = true;
56 }
57 else
58 {
59 aIsList = false;
60 }
61
62 if (aArgs->ParseAsUint16(aNumEntries) == OT_ERROR_NONE)
63 {
64 aArgs++;
65 }
66 else
67 {
68 aNumEntries = 0;
69 }
70
71 return aArgs[0].IsEmpty() ? OT_ERROR_NONE : OT_ERROR_INVALID_ARGS;
72 }
73
Process(Arg aArgs[])74 template <> otError History::Process<Cmd("ipaddr")>(Arg aArgs[])
75 {
76 otError error;
77 bool isList;
78 uint16_t numEntries;
79 otHistoryTrackerIterator iterator;
80 const otHistoryTrackerUnicastAddressInfo *info;
81 uint32_t entryAge;
82 char ageString[OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE];
83 char addressString[OT_IP6_ADDRESS_STRING_SIZE + 4];
84
85 static_assert(0 == OT_HISTORY_TRACKER_ADDRESS_EVENT_ADDED, "ADDRESS_EVENT_ADDED is incorrect");
86 static_assert(1 == OT_HISTORY_TRACKER_ADDRESS_EVENT_REMOVED, "ADDRESS_EVENT_REMOVED is incorrect");
87
88 SuccessOrExit(error = ParseArgs(aArgs, isList, numEntries));
89
90 if (!isList)
91 {
92 // | Age | Event | Address / PrefixLen /123 | Origin |Scope| P | V | R |
93 // +----------------------+---------+---------------------------------------------+--------+-----+---+---+---+
94
95 static const char *const kUnicastAddrInfoTitles[] = {
96 "Age", "Event", "Address / PrefixLength", "Origin", "Scope", "P", "V", "R"};
97
98 static const uint8_t kUnicastAddrInfoColumnWidths[] = {22, 9, 45, 8, 5, 3, 3, 3};
99
100 OutputTableHeader(kUnicastAddrInfoTitles, kUnicastAddrInfoColumnWidths);
101 }
102
103 otHistoryTrackerInitIterator(&iterator);
104
105 for (uint16_t index = 0; (numEntries == 0) || (index < numEntries); index++)
106 {
107 info = otHistoryTrackerIterateUnicastAddressHistory(GetInstancePtr(), &iterator, &entryAge);
108 VerifyOrExit(info != nullptr);
109
110 otHistoryTrackerEntryAgeToString(entryAge, ageString, sizeof(ageString));
111 otIp6AddressToString(&info->mAddress, addressString, sizeof(addressString));
112
113 if (!isList)
114 {
115 size_t len = strlen(addressString);
116
117 snprintf(&addressString[len], sizeof(addressString) - len, "/%d", info->mPrefixLength);
118
119 OutputLine("| %20s | %-7s | %-43s | %-6s | %3d | %c | %c | %c |", ageString,
120 Stringify(info->mEvent, kSimpleEventStrings), addressString,
121 Interpreter::AddressOriginToString(info->mAddressOrigin), info->mScope,
122 info->mPreferred ? 'Y' : 'N', info->mValid ? 'Y' : 'N', info->mRloc ? 'Y' : 'N');
123 }
124 else
125 {
126 OutputLine("%s -> event:%s address:%s prefixlen:%d origin:%s scope:%d preferred:%s valid:%s rloc:%s",
127 ageString, Stringify(info->mEvent, kSimpleEventStrings), addressString, info->mPrefixLength,
128 Interpreter::AddressOriginToString(info->mAddressOrigin), info->mScope,
129 info->mPreferred ? "yes" : "no", info->mValid ? "yes" : "no", info->mRloc ? "yes" : "no");
130 }
131 }
132
133 exit:
134 return error;
135 }
136
Process(Arg aArgs[])137 template <> otError History::Process<Cmd("ipmaddr")>(Arg aArgs[])
138 {
139 static const char *const kEventStrings[] = {
140 "Subscribed", // (0) OT_HISTORY_TRACKER_ADDRESS_EVENT_ADDED
141 "Unsubscribed" // (1) OT_HISTORY_TRACKER_ADDRESS_EVENT_REMOVED
142 };
143
144 otError error;
145 bool isList;
146 uint16_t numEntries;
147 otHistoryTrackerIterator iterator;
148 const otHistoryTrackerMulticastAddressInfo *info;
149 uint32_t entryAge;
150 char ageString[OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE];
151 char addressString[OT_IP6_ADDRESS_STRING_SIZE];
152
153 static_assert(0 == OT_HISTORY_TRACKER_ADDRESS_EVENT_ADDED, "ADDRESS_EVENT_ADDED is incorrect");
154 static_assert(1 == OT_HISTORY_TRACKER_ADDRESS_EVENT_REMOVED, "ADDRESS_EVENT_REMOVED is incorrect");
155
156 SuccessOrExit(error = ParseArgs(aArgs, isList, numEntries));
157
158 if (!isList)
159 {
160 // | Age | Event | Multicast Address | Origin |
161 // +----------------------+--------------+-----------------------------------------+--------+
162
163 static const char *const kMulticastAddrInfoTitles[] = {
164 "Age",
165 "Event",
166 "Multicast Address",
167 "Origin",
168 };
169
170 static const uint8_t kMulticastAddrInfoColumnWidths[] = {22, 14, 42, 8};
171
172 OutputTableHeader(kMulticastAddrInfoTitles, kMulticastAddrInfoColumnWidths);
173 }
174
175 otHistoryTrackerInitIterator(&iterator);
176
177 for (uint16_t index = 0; (numEntries == 0) || (index < numEntries); index++)
178 {
179 info = otHistoryTrackerIterateMulticastAddressHistory(GetInstancePtr(), &iterator, &entryAge);
180 VerifyOrExit(info != nullptr);
181
182 otHistoryTrackerEntryAgeToString(entryAge, ageString, sizeof(ageString));
183 otIp6AddressToString(&info->mAddress, addressString, sizeof(addressString));
184
185 OutputLine(isList ? "%s -> event:%s address:%s origin:%s" : "| %20s | %-12s | %-39s | %-6s |", ageString,
186 Stringify(info->mEvent, kEventStrings), addressString,
187 Interpreter::AddressOriginToString(info->mAddressOrigin));
188 }
189
190 exit:
191 return error;
192 }
193
Process(Arg aArgs[])194 template <> otError History::Process<Cmd("neighbor")>(Arg aArgs[])
195 {
196 static const char *const kEventString[] = {
197 /* (0) OT_HISTORY_TRACKER_NEIGHBOR_EVENT_ADDED -> */ "Added",
198 /* (1) OT_HISTORY_TRACKER_NEIGHBOR_EVENT_REMOVED -> */ "Removed",
199 /* (2) OT_HISTORY_TRACKER_NEIGHBOR_EVENT_CHANGED -> */ "Changed",
200 /* (3) OT_HISTORY_TRACKER_NEIGHBOR_EVENT_RESTORING -> */ "Restoring",
201 };
202
203 otError error;
204 bool isList;
205 uint16_t numEntries;
206 otHistoryTrackerIterator iterator;
207 const otHistoryTrackerNeighborInfo *info;
208 uint32_t entryAge;
209 char ageString[OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE];
210 otLinkModeConfig mode;
211 char linkModeString[Interpreter::kLinkModeStringSize];
212
213 static_assert(0 == OT_HISTORY_TRACKER_NEIGHBOR_EVENT_ADDED, "NEIGHBOR_EVENT_ADDED value is incorrect");
214 static_assert(1 == OT_HISTORY_TRACKER_NEIGHBOR_EVENT_REMOVED, "NEIGHBOR_EVENT_REMOVED value is incorrect");
215 static_assert(2 == OT_HISTORY_TRACKER_NEIGHBOR_EVENT_CHANGED, "NEIGHBOR_EVENT_CHANGED value is incorrect");
216 static_assert(3 == OT_HISTORY_TRACKER_NEIGHBOR_EVENT_RESTORING, "NEIGHBOR_EVENT_RESTORING value is incorrect");
217
218 SuccessOrExit(error = ParseArgs(aArgs, isList, numEntries));
219
220 if (!isList)
221 {
222 // | Age | Type | Event | Extended Address | RLOC16 | Mode | Ave RSS |
223 // +----------------------+--------+-----------+------------------+--------+------+---------+
224
225 static const char *const kNeighborInfoTitles[] = {
226 "Age", "Type", "Event", "Extended Address", "RLOC16", "Mode", "Ave RSS",
227 };
228
229 static const uint8_t kNeighborInfoColumnWidths[] = {22, 8, 11, 18, 8, 6, 9};
230
231 OutputTableHeader(kNeighborInfoTitles, kNeighborInfoColumnWidths);
232 }
233
234 otHistoryTrackerInitIterator(&iterator);
235
236 for (uint16_t index = 0; (numEntries == 0) || (index < numEntries); index++)
237 {
238 info = otHistoryTrackerIterateNeighborHistory(GetInstancePtr(), &iterator, &entryAge);
239 VerifyOrExit(info != nullptr);
240
241 otHistoryTrackerEntryAgeToString(entryAge, ageString, sizeof(ageString));
242
243 mode.mRxOnWhenIdle = info->mRxOnWhenIdle;
244 mode.mDeviceType = info->mFullThreadDevice;
245 mode.mNetworkData = info->mFullNetworkData;
246 Interpreter::LinkModeToString(mode, linkModeString);
247
248 OutputFormat(isList ? "%s -> type:%s event:%s extaddr:" : "| %20s | %-6s | %-9s | ", ageString,
249 info->mIsChild ? "Child" : "Router", kEventString[info->mEvent]);
250 OutputExtAddress(info->mExtAddress);
251 OutputLine(isList ? " rloc16:0x%04x mode:%s rss:%d" : " | 0x%04x | %-4s | %7d |", info->mRloc16, linkModeString,
252 info->mAverageRssi);
253 }
254
255 exit:
256 return error;
257 }
258
Process(Arg aArgs[])259 template <> otError History::Process<Cmd("netinfo")>(Arg aArgs[])
260 {
261 otError error;
262 bool isList;
263 uint16_t numEntries;
264 otHistoryTrackerIterator iterator;
265 const otHistoryTrackerNetworkInfo *info;
266 uint32_t entryAge;
267 char ageString[OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE];
268 char linkModeString[Interpreter::kLinkModeStringSize];
269
270 SuccessOrExit(error = ParseArgs(aArgs, isList, numEntries));
271
272 if (!isList)
273 {
274 // | Age | Role | Mode | RLOC16 | Partition ID |
275 // +----------------------+----------+------+--------+--------------+
276
277 static const char *const kNetInfoTitles[] = {"Age", "Role", "Mode", "RLOC16", "Partition ID"};
278 static const uint8_t kNetInfoColumnWidths[] = {22, 10, 6, 8, 14};
279
280 OutputTableHeader(kNetInfoTitles, kNetInfoColumnWidths);
281 }
282
283 otHistoryTrackerInitIterator(&iterator);
284
285 for (uint16_t index = 0; (numEntries == 0) || (index < numEntries); index++)
286 {
287 info = otHistoryTrackerIterateNetInfoHistory(GetInstancePtr(), &iterator, &entryAge);
288 VerifyOrExit(info != nullptr);
289
290 otHistoryTrackerEntryAgeToString(entryAge, ageString, sizeof(ageString));
291
292 OutputLine(isList ? "%s -> role:%s mode:%s rloc16:0x%04x partition-id:%u"
293 : "| %20s | %-8s | %-4s | 0x%04x | %12u |",
294 ageString, otThreadDeviceRoleToString(info->mRole),
295 Interpreter::LinkModeToString(info->mMode, linkModeString), info->mRloc16, info->mPartitionId);
296 }
297
298 exit:
299 return error;
300 }
301
Process(Arg aArgs[])302 template <> otError History::Process<Cmd("rx")>(Arg aArgs[])
303 {
304 return ProcessRxTxHistory(kRx, aArgs);
305 }
306
Process(Arg aArgs[])307 template <> otError History::Process<Cmd("rxtx")>(Arg aArgs[])
308 {
309 return ProcessRxTxHistory(kRxTx, aArgs);
310 }
311
Process(Arg aArgs[])312 template <> otError History::Process<Cmd("tx")>(Arg aArgs[])
313 {
314 return ProcessRxTxHistory(kTx, aArgs);
315 }
316
MessagePriorityToString(uint8_t aPriority)317 const char *History::MessagePriorityToString(uint8_t aPriority)
318 {
319 static const char *const kPriorityStrings[] = {
320 "low", // (0) OT_HISTORY_TRACKER_MSG_PRIORITY_LOW
321 "norm", // (1) OT_HISTORY_TRACKER_MSG_PRIORITY_NORMAL
322 "high", // (2) OT_HISTORY_TRACKER_MSG_PRIORITY_HIGH
323 "net", // (3) OT_HISTORY_TRACKER_MSG_PRIORITY_NET
324 };
325
326 static_assert(0 == OT_HISTORY_TRACKER_MSG_PRIORITY_LOW, "MSG_PRIORITY_LOW value is incorrect");
327 static_assert(1 == OT_HISTORY_TRACKER_MSG_PRIORITY_NORMAL, "MSG_PRIORITY_NORMAL value is incorrect");
328 static_assert(2 == OT_HISTORY_TRACKER_MSG_PRIORITY_HIGH, "MSG_PRIORITY_HIGH value is incorrect");
329 static_assert(3 == OT_HISTORY_TRACKER_MSG_PRIORITY_NET, "MSG_PRIORITY_NET value is incorrect");
330
331 return Stringify(aPriority, kPriorityStrings, "unkn");
332 }
333
RadioTypeToString(const otHistoryTrackerMessageInfo & aInfo)334 const char *History::RadioTypeToString(const otHistoryTrackerMessageInfo &aInfo)
335 {
336 const char *str = "none";
337
338 if (aInfo.mRadioTrelUdp6 && aInfo.mRadioIeee802154)
339 {
340 str = "all";
341 }
342 else if (aInfo.mRadioIeee802154)
343 {
344 str = "15.4";
345 }
346 else if (aInfo.mRadioTrelUdp6)
347 {
348 str = "trel";
349 }
350
351 return str;
352 }
353
MessageTypeToString(const otHistoryTrackerMessageInfo & aInfo)354 const char *History::MessageTypeToString(const otHistoryTrackerMessageInfo &aInfo)
355 {
356 const char *str = otIp6ProtoToString(aInfo.mIpProto);
357
358 if (aInfo.mIpProto == OT_IP6_PROTO_ICMP6)
359 {
360 switch (aInfo.mIcmp6Type)
361 {
362 case OT_ICMP6_TYPE_DST_UNREACH:
363 str = "ICMP6(Unreach)";
364 break;
365 case OT_ICMP6_TYPE_PACKET_TO_BIG:
366 str = "ICMP6(TooBig)";
367 break;
368 case OT_ICMP6_TYPE_ECHO_REQUEST:
369 str = "ICMP6(EchoReqst)";
370 break;
371 case OT_ICMP6_TYPE_ECHO_REPLY:
372 str = "ICMP6(EchoReply)";
373 break;
374 case OT_ICMP6_TYPE_ROUTER_SOLICIT:
375 str = "ICMP6(RouterSol)";
376 break;
377 case OT_ICMP6_TYPE_ROUTER_ADVERT:
378 str = "ICMP6(RouterAdv)";
379 break;
380 default:
381 str = "ICMP6(Other)";
382 break;
383 }
384 }
385
386 return str;
387 }
388
ProcessRxTxHistory(RxTx aRxTx,Arg aArgs[])389 otError History::ProcessRxTxHistory(RxTx aRxTx, Arg aArgs[])
390 {
391 otError error;
392 bool isList;
393 uint16_t numEntries;
394 otHistoryTrackerIterator rxIterator;
395 otHistoryTrackerIterator txIterator;
396 bool isRx = false;
397 const otHistoryTrackerMessageInfo *info = nullptr;
398 const otHistoryTrackerMessageInfo *rxInfo = nullptr;
399 const otHistoryTrackerMessageInfo *txInfo = nullptr;
400 uint32_t entryAge;
401 uint32_t rxEntryAge;
402 uint32_t txEntryAge;
403
404 // | Age | Type | Len | Chksum | Sec | Prio | RSS |Dir | Neighb | Radio |
405 // +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
406
407 static const char *const kTableTitles[] = {"Age", "Type", "Len", "Chksum", "Sec",
408 "Prio", "RSS", "Dir", "Neighb", "Radio"};
409
410 static const uint8_t kTableColumnWidths[] = {22, 18, 7, 8, 5, 6, 6, 4, 8, 7};
411
412 SuccessOrExit(error = ParseArgs(aArgs, isList, numEntries));
413
414 if (!isList)
415 {
416 OutputTableHeader(kTableTitles, kTableColumnWidths);
417 }
418
419 otHistoryTrackerInitIterator(&txIterator);
420 otHistoryTrackerInitIterator(&rxIterator);
421
422 for (uint16_t index = 0; (numEntries == 0) || (index < numEntries); index++)
423 {
424 switch (aRxTx)
425 {
426 case kRx:
427 info = otHistoryTrackerIterateRxHistory(GetInstancePtr(), &rxIterator, &entryAge);
428 isRx = true;
429 break;
430
431 case kTx:
432 info = otHistoryTrackerIterateTxHistory(GetInstancePtr(), &txIterator, &entryAge);
433 isRx = false;
434 break;
435
436 case kRxTx:
437 // Iterate through both RX and TX lists and determine the entry
438 // with earlier age.
439
440 if (rxInfo == nullptr)
441 {
442 rxInfo = otHistoryTrackerIterateRxHistory(GetInstancePtr(), &rxIterator, &rxEntryAge);
443 }
444
445 if (txInfo == nullptr)
446 {
447 txInfo = otHistoryTrackerIterateTxHistory(GetInstancePtr(), &txIterator, &txEntryAge);
448 }
449
450 if ((rxInfo != nullptr) && ((txInfo == nullptr) || (rxEntryAge <= txEntryAge)))
451 {
452 info = rxInfo;
453 entryAge = rxEntryAge;
454 isRx = true;
455 rxInfo = nullptr;
456 }
457 else
458 {
459 info = txInfo;
460 entryAge = txEntryAge;
461 isRx = false;
462 txInfo = nullptr;
463 }
464
465 break;
466 }
467
468 VerifyOrExit(info != nullptr);
469
470 if (isList)
471 {
472 OutputRxTxEntryListFormat(*info, entryAge, isRx);
473 }
474 else
475 {
476 if (index != 0)
477 {
478 OutputTableSeparator(kTableColumnWidths);
479 }
480
481 OutputRxTxEntryTableFormat(*info, entryAge, isRx);
482 }
483 }
484
485 exit:
486 return error;
487 }
488
OutputRxTxEntryListFormat(const otHistoryTrackerMessageInfo & aInfo,uint32_t aEntryAge,bool aIsRx)489 void History::OutputRxTxEntryListFormat(const otHistoryTrackerMessageInfo &aInfo, uint32_t aEntryAge, bool aIsRx)
490 {
491 constexpr uint8_t kIndentSize = 4;
492
493 char ageString[OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE];
494
495 otHistoryTrackerEntryAgeToString(aEntryAge, ageString, sizeof(ageString));
496
497 OutputLine("%s", ageString);
498 OutputFormat(kIndentSize, "type:%s len:%u cheksum:0x%04x sec:%s prio:%s ", MessageTypeToString(aInfo),
499 aInfo.mPayloadLength, aInfo.mChecksum, aInfo.mLinkSecurity ? "yes" : "no",
500 MessagePriorityToString(aInfo.mPriority));
501 if (aIsRx)
502 {
503 OutputFormat("rss:%d", aInfo.mAveRxRss);
504 }
505 else
506 {
507 OutputFormat("tx-success:%s", aInfo.mTxSuccess ? "yes" : "no");
508 }
509
510 OutputLine(" %s:0x%04x radio:%s", aIsRx ? "from" : "to", aInfo.mNeighborRloc16, RadioTypeToString(aInfo));
511
512 OutputFormat(kIndentSize, "src:");
513 OutputSockAddrLine(aInfo.mSource);
514
515 OutputFormat(kIndentSize, "dst:");
516 OutputSockAddrLine(aInfo.mDestination);
517 }
518
OutputRxTxEntryTableFormat(const otHistoryTrackerMessageInfo & aInfo,uint32_t aEntryAge,bool aIsRx)519 void History::OutputRxTxEntryTableFormat(const otHistoryTrackerMessageInfo &aInfo, uint32_t aEntryAge, bool aIsRx)
520 {
521 char ageString[OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE];
522 char addrString[OT_IP6_SOCK_ADDR_STRING_SIZE];
523
524 otHistoryTrackerEntryAgeToString(aEntryAge, ageString, sizeof(ageString));
525
526 OutputFormat("| %20s | %-16.16s | %5u | 0x%04x | %3s | %4s | ", "", MessageTypeToString(aInfo),
527 aInfo.mPayloadLength, aInfo.mChecksum, aInfo.mLinkSecurity ? "yes" : "no",
528 MessagePriorityToString(aInfo.mPriority));
529
530 if (aIsRx)
531 {
532 OutputFormat("%4d | RX ", aInfo.mAveRxRss);
533 }
534 else
535 {
536 OutputFormat(" NA |");
537 OutputFormat(aInfo.mTxSuccess ? " TX " : "TX-F");
538 }
539
540 if (aInfo.mNeighborRloc16 == kShortAddrBroadcast)
541 {
542 OutputFormat("| bcast ");
543 }
544 else if (aInfo.mNeighborRloc16 == kShortAddrInvalid)
545 {
546 OutputFormat("| unknwn ");
547 }
548 else
549 {
550 OutputFormat("| 0x%04x ", aInfo.mNeighborRloc16);
551 }
552
553 OutputLine("| %5.5s |", RadioTypeToString(aInfo));
554
555 otIp6SockAddrToString(&aInfo.mSource, addrString, sizeof(addrString));
556 OutputLine("| %20s | src: %-70s |", ageString, addrString);
557
558 otIp6SockAddrToString(&aInfo.mDestination, addrString, sizeof(addrString));
559 OutputLine("| %20s | dst: %-70s |", "", addrString);
560 }
561
Process(Arg aArgs[])562 template <> otError History::Process<Cmd("prefix")>(Arg aArgs[])
563 {
564 otError error;
565 bool isList;
566 uint16_t numEntries;
567 otHistoryTrackerIterator iterator;
568 const otHistoryTrackerOnMeshPrefixInfo *info;
569 uint32_t entryAge;
570 char ageString[OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE];
571 char prefixString[OT_IP6_PREFIX_STRING_SIZE];
572 NetworkData::FlagsString flagsString;
573
574 static_assert(0 == OT_HISTORY_TRACKER_NET_DATA_ENTRY_ADDED, "NET_DATA_ENTRY_ADDED value is incorrect");
575 static_assert(1 == OT_HISTORY_TRACKER_NET_DATA_ENTRY_REMOVED, "NET_DATA_ENTRY_REMOVED value is incorrect");
576
577 SuccessOrExit(error = ParseArgs(aArgs, isList, numEntries));
578
579 if (!isList)
580 {
581 // | Age | Event | Prefix | Flags | Pref | RLOC16 |
582 // +----------------------+---------+---------------------------------------------+-----------+------+--------+
583
584 static const char *const kPrefixTitles[] = {"Age", "Event", "Prefix", "Flags", "Pref", "RLOC16"};
585 static const uint8_t kPrefixColumnWidths[] = {22, 9, 45, 11, 6, 8};
586
587 OutputTableHeader(kPrefixTitles, kPrefixColumnWidths);
588 }
589
590 otHistoryTrackerInitIterator(&iterator);
591
592 for (uint16_t index = 0; (numEntries == 0) || (index < numEntries); index++)
593 {
594 info = otHistoryTrackerIterateOnMeshPrefixHistory(GetInstancePtr(), &iterator, &entryAge);
595 VerifyOrExit(info != nullptr);
596
597 otHistoryTrackerEntryAgeToString(entryAge, ageString, sizeof(ageString));
598
599 otIp6PrefixToString(&info->mPrefix.mPrefix, prefixString, sizeof(prefixString));
600 NetworkData::PrefixFlagsToString(info->mPrefix, flagsString);
601
602 OutputLine(isList ? "%s -> event:%s prefix:%s flags:%s pref:%s rloc16:0x%04x"
603 : "| %20s | %-7s | %-43s | %-9s | %-4s | 0x%04x |",
604 ageString, Stringify(info->mEvent, kSimpleEventStrings), prefixString, flagsString,
605 Interpreter::PreferenceToString(info->mPrefix.mPreference), info->mPrefix.mRloc16);
606 }
607
608 exit:
609 return error;
610 }
611
Process(Arg aArgs[])612 template <> otError History::Process<Cmd("route")>(Arg aArgs[])
613 {
614 otError error;
615 bool isList;
616 uint16_t numEntries;
617 otHistoryTrackerIterator iterator;
618 const otHistoryTrackerExternalRouteInfo *info;
619 uint32_t entryAge;
620 char ageString[OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE];
621 char prefixString[OT_IP6_PREFIX_STRING_SIZE];
622 NetworkData::FlagsString flagsString;
623
624 static_assert(0 == OT_HISTORY_TRACKER_NET_DATA_ENTRY_ADDED, "NET_DATA_ENTRY_ADDED value is incorrect");
625 static_assert(1 == OT_HISTORY_TRACKER_NET_DATA_ENTRY_REMOVED, "NET_DATA_ENTRY_REMOVED value is incorrect");
626
627 SuccessOrExit(error = ParseArgs(aArgs, isList, numEntries));
628
629 if (!isList)
630 {
631 // | Age | Event | Route | Flags | Pref | RLOC16 |
632 // +----------------------+---------+---------------------------------------------+-----------+------+--------+
633
634 static const char *const kRouteTitles[] = {"Age", "Event", "Route", "Flags", "Pref", "RLOC16"};
635 static const uint8_t kRouteColumnWidths[] = {22, 9, 45, 11, 6, 8};
636
637 OutputTableHeader(kRouteTitles, kRouteColumnWidths);
638 }
639
640 otHistoryTrackerInitIterator(&iterator);
641
642 for (uint16_t index = 0; (numEntries == 0) || (index < numEntries); index++)
643 {
644 info = otHistoryTrackerIterateExternalRouteHistory(GetInstancePtr(), &iterator, &entryAge);
645 VerifyOrExit(info != nullptr);
646
647 otHistoryTrackerEntryAgeToString(entryAge, ageString, sizeof(ageString));
648
649 otIp6PrefixToString(&info->mRoute.mPrefix, prefixString, sizeof(prefixString));
650 NetworkData::RouteFlagsToString(info->mRoute, flagsString);
651
652 OutputLine(isList ? "%s -> event:%s route:%s flags:%s pref:%s rloc16:0x%04x"
653 : "| %20s | %-7s | %-43s | %-9s | %-4s | 0x%04x |",
654 ageString, Stringify(info->mEvent, kSimpleEventStrings), prefixString, flagsString,
655 Interpreter::PreferenceToString(info->mRoute.mPreference), info->mRoute.mRloc16);
656 }
657
658 exit:
659 return error;
660 }
661
Process(Arg aArgs[])662 otError History::Process(Arg aArgs[])
663 {
664 #define CmdEntry(aCommandString) \
665 { \
666 aCommandString, &History::Process<Cmd(aCommandString)> \
667 }
668
669 static constexpr Command kCommands[] = {
670 CmdEntry("ipaddr"), CmdEntry("ipmaddr"), CmdEntry("neighbor"), CmdEntry("netinfo"), CmdEntry("prefix"),
671 CmdEntry("route"), CmdEntry("rx"), CmdEntry("rxtx"), CmdEntry("tx"),
672 };
673
674 #undef CmdEntry
675
676 static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted");
677
678 otError error = OT_ERROR_INVALID_COMMAND;
679 const Command *command;
680
681 if (aArgs[0].IsEmpty() || (aArgs[0] == "help"))
682 {
683 OutputCommandTable(kCommands);
684 ExitNow(error = aArgs[0].IsEmpty() ? error : OT_ERROR_NONE);
685 }
686
687 command = BinarySearch::Find(aArgs[0].GetCString(), kCommands);
688 VerifyOrExit(command != nullptr);
689
690 error = (this->*command->mHandler)(aArgs + 1);
691
692 exit:
693 return error;
694 }
695
696 } // namespace Cli
697 } // namespace ot
698
699 #endif // OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
700