• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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