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/ADDRESS_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 
74 /**
75  * @cli history ipaddr
76  * @code
77  * history ipaddr
78  * | Age                  | Event   | Address / Prefix Length                     | Origin |Scope| P | V | R |
79  * +----------------------+---------+---------------------------------------------+--------+-----+---+---+---+
80  * |         00:00:04.991 | Removed | 2001:dead:beef:cafe:c4cb:caba:8d55:e30b/64  | slaac  |  14 | Y | Y | N |
81  * |         00:00:44.647 | Added   | 2001:dead:beef:cafe:c4cb:caba:8d55:e30b/64  | slaac  |  14 | Y | Y | N |
82  * |         00:01:07.199 | Added   | fd00:0:0:0:0:0:0:1/64                       | manual |  14 | Y | Y | N |
83  * |         00:02:17.885 | Added   | fdde:ad00:beef:0:0:ff:fe00:fc00/64          | thread |   3 | N | Y | N |
84  * |         00:02:17.885 | Added   | fdde:ad00:beef:0:0:ff:fe00:5400/64          | thread |   3 | N | Y | Y |
85  * |         00:02:20.107 | Removed | fdde:ad00:beef:0:0:ff:fe00:5400/64          | thread |   3 | N | Y | Y |
86  * |         00:02:21.575 | Added   | fdde:ad00:beef:0:0:ff:fe00:5400/64          | thread |   3 | N | Y | Y |
87  * |         00:02:21.575 | Added   | fdde:ad00:beef:0:ecea:c4fc:ad96:4655/64     | thread |   3 | N | Y | N |
88  * |         00:02:23.904 | Added   | fe80:0:0:0:3c12:a4d2:fbe0:31ad/64           | thread |   2 | Y | Y | N |
89  * Done
90  * @endcode
91  * @code
92  * history ipaddr list 5
93  * 00:00:20.327 -> event:Removed address:2001:dead:beef:cafe:c4cb:caba:8d55:e30b <!--
94  * -->prefixlen:64 origin:slaac scope:14 preferred:yes valid:yes rloc:no
95  * 00:00:59.983 -> event:Added address:2001:dead:beef:cafe:c4cb:caba:8d55:e30b <!--
96  * -->prefixlen:64 origin:slaac scope:14 preferred:yes valid:yes rloc:no
97  * 00:01:22.535 -> event:Added address:fd00:0:0:0:0:0:0:1 prefixlen:64 <!--
98  * -->origin:manual scope:14 preferred:yes valid:yes rloc:no
99  * 00:02:33.221 -> event:Added address:fdde:ad00:beef:0:0:ff:fe00:fc00 <!--
100  * -->prefixlen:64 origin:thread scope:3 preferred:no valid:yes rloc:no
101  * 00:02:33.221 -> event:Added address:fdde:ad00:beef:0:0:ff:fe00:5400 <!--
102  * -->prefixlen:64 origin:thread scope:3 preferred:no valid:yes rloc:yes
103  * Done
104  * @endcode
105  * @cparam history ipaddr [@ca{list}] [@ca{num-entries}]
106  * * Use the `list` option to display the output in list format. Otherwise,
107  *   the output is shown in table format.
108  * * Use the `num-entries` option to limit the output to the number of
109  *   most-recent entries specified. If this option is not used, all stored
110  *   entries are shown in the output.
111  * @par
112  * Displays the unicast IPv6 address history in table or list format.
113  * @par
114  * Each table or list entry provides:
115  * * Age: Time elapsed since the command was issued, and given in the format:
116  *        `hours`:`minutes`:`seconds`:`milliseconds`
117  * * Event: Possible values are `Added` or `Removed`.
118  * * Address/Prefix Length: Unicast address with its prefix length (in bits).
119  * * Origin: Possible value are `thread`, `slaac`, `dhcp6`, or `manual`.
120  * * Scope: IPv6 address scope.
121  * * P: Preferred flag.
122  * * V: Valid flag.
123  * * RLOC (R): This flag indicates if the IPv6 address is a routing locator.
124  * @note
125  * All commands under `history` require the `OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE`
126  * feature to be enabled.
127  * @sa otHistoryTrackerEntryAgeToString
128  * @sa otHistoryTrackerIterateUnicastAddressHistory
129  */
Process(Arg aArgs[])130 template <> otError History::Process<Cmd("ipaddr")>(Arg aArgs[])
131 {
132     otError                                   error;
133     bool                                      isList;
134     uint16_t                                  numEntries;
135     otHistoryTrackerIterator                  iterator;
136     const otHistoryTrackerUnicastAddressInfo *info;
137     uint32_t                                  entryAge;
138     char                                      ageString[OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE];
139     char                                      addressString[OT_IP6_ADDRESS_STRING_SIZE + 4];
140 
141     static_assert(0 == OT_HISTORY_TRACKER_ADDRESS_EVENT_ADDED, "ADDRESS_EVENT_ADDED is incorrect");
142     static_assert(1 == OT_HISTORY_TRACKER_ADDRESS_EVENT_REMOVED, "ADDRESS_EVENT_REMOVED is incorrect");
143 
144     SuccessOrExit(error = ParseArgs(aArgs, isList, numEntries));
145 
146     if (!isList)
147     {
148         // | Age                  | Event   | Address / PrefixLen                  /123   | Origin |Scope| P | V | R |
149         // +----------------------+---------+---------------------------------------------+--------+-----+---+---+---+
150 
151         static const char *const kUnicastAddrInfoTitles[] = {
152             "Age", "Event", "Address / PrefixLength", "Origin", "Scope", "P", "V", "R"};
153 
154         static const uint8_t kUnicastAddrInfoColumnWidths[] = {22, 9, 45, 8, 5, 3, 3, 3};
155 
156         OutputTableHeader(kUnicastAddrInfoTitles, kUnicastAddrInfoColumnWidths);
157     }
158 
159     otHistoryTrackerInitIterator(&iterator);
160 
161     for (uint16_t index = 0; (numEntries == 0) || (index < numEntries); index++)
162     {
163         info = otHistoryTrackerIterateUnicastAddressHistory(GetInstancePtr(), &iterator, &entryAge);
164         VerifyOrExit(info != nullptr);
165 
166         otHistoryTrackerEntryAgeToString(entryAge, ageString, sizeof(ageString));
167         otIp6AddressToString(&info->mAddress, addressString, sizeof(addressString));
168 
169         if (!isList)
170         {
171             size_t len = strlen(addressString);
172 
173             snprintf(&addressString[len], sizeof(addressString) - len, "/%d", info->mPrefixLength);
174 
175             OutputLine("| %20s | %-7s | %-43s | %-6s | %3d | %c | %c | %c |", ageString,
176                        Stringify(info->mEvent, kSimpleEventStrings), addressString,
177                        Interpreter::AddressOriginToString(info->mAddressOrigin), info->mScope,
178                        info->mPreferred ? 'Y' : 'N', info->mValid ? 'Y' : 'N', info->mRloc ? 'Y' : 'N');
179         }
180         else
181         {
182             OutputLine("%s -> event:%s address:%s prefixlen:%d origin:%s scope:%d preferred:%s valid:%s rloc:%s",
183                        ageString, Stringify(info->mEvent, kSimpleEventStrings), addressString, info->mPrefixLength,
184                        Interpreter::AddressOriginToString(info->mAddressOrigin), info->mScope,
185                        info->mPreferred ? "yes" : "no", info->mValid ? "yes" : "no", info->mRloc ? "yes" : "no");
186         }
187     }
188 
189 exit:
190     return error;
191 }
192 
193 /**
194  * @cli history ipmaddr
195  * @code
196  * history ipmaddr
197  * | Age                  | Event        | Multicast Address                       | Origin |
198  * +----------------------+--------------+-----------------------------------------+--------+
199  * |         00:00:08.592 | Unsubscribed | ff05:0:0:0:0:0:0:1                      | Manual |
200  * |         00:01:25.353 | Subscribed   | ff05:0:0:0:0:0:0:1                      | Manual |
201  * |         00:01:54.953 | Subscribed   | ff03:0:0:0:0:0:0:2                      | Thread |
202  * |         00:01:54.953 | Subscribed   | ff02:0:0:0:0:0:0:2                      | Thread |
203  * |         00:01:59.329 | Subscribed   | ff33:40:fdde:ad00:beef:0:0:1            | Thread |
204  * |         00:01:59.329 | Subscribed   | ff32:40:fdde:ad00:beef:0:0:1            | Thread |
205  * |         00:02:01.129 | Subscribed   | ff03:0:0:0:0:0:0:fc                     | Thread |
206  * |         00:02:01.129 | Subscribed   | ff03:0:0:0:0:0:0:1                      | Thread |
207  * |         00:02:01.129 | Subscribed   | ff02:0:0:0:0:0:0:1                      | Thread |
208  * Done
209  * @endcode
210  * @code
211  * history ipmaddr list
212  * 00:00:25.447 -> event:Unsubscribed address:ff05:0:0:0:0:0:0:1 origin:Manual
213  * 00:01:42.208 -> event:Subscribed address:ff05:0:0:0:0:0:0:1 origin:Manual
214  * 00:02:11.808 -> event:Subscribed address:ff03:0:0:0:0:0:0:2 origin:Thread
215  * 00:02:11.808 -> event:Subscribed address:ff02:0:0:0:0:0:0:2 origin:Thread
216  * 00:02:16.184 -> event:Subscribed address:ff33:40:fdde:ad00:beef:0:0:1 origin:Thread
217  * 00:02:16.184 -> event:Subscribed address:ff32:40:fdde:ad00:beef:0:0:1 origin:Thread
218  * 00:02:17.984 -> event:Subscribed address:ff03:0:0:0:0:0:0:fc origin:Thread
219  * 00:02:17.984 -> event:Subscribed address:ff03:0:0:0:0:0:0:1 origin:Thread
220  * 00:02:17.984 -> event:Subscribed address:ff02:0:0:0:0:0:0:1 origin:Thread
221  * Done
222  * @endcode
223  * @cparam history ipmaddr [@ca{list}] [@ca{num-entries}]
224  * * Use the `list` option to display the output in list format. Otherwise,
225  *   the output is shown in table format.
226  * * Use the `num-entries` option to limit the output to the number of
227  *   most-recent entries specified. If this option is not used, all stored
228  *   entries are shown in the output.
229  * @par
230  * Displays the multicast IPv6 address history in table or list format.
231  * @par
232  * Each table or list entry provides:
233  * * Age: Time elapsed since the command was issued, and given in the format:
234  *        `hours`:`minutes`:`seconds`:`milliseconds`
235  * * Event: Possible values are `Subscribed` or `Unsubscribed`.
236  * * Multicast Address
237  * * Origin: Possible values are `Thread` or `Manual`.
238  * @sa otHistoryTrackerEntryAgeToString
239  * @sa otHistoryTrackerIterateMulticastAddressHistory
240  */
Process(Arg aArgs[])241 template <> otError History::Process<Cmd("ipmaddr")>(Arg aArgs[])
242 {
243     static const char *const kEventStrings[] = {
244         "Subscribed",  // (0) OT_HISTORY_TRACKER_ADDRESS_EVENT_ADDED
245         "Unsubscribed" // (1) OT_HISTORY_TRACKER_ADDRESS_EVENT_REMOVED
246     };
247 
248     otError                                     error;
249     bool                                        isList;
250     uint16_t                                    numEntries;
251     otHistoryTrackerIterator                    iterator;
252     const otHistoryTrackerMulticastAddressInfo *info;
253     uint32_t                                    entryAge;
254     char                                        ageString[OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE];
255     char                                        addressString[OT_IP6_ADDRESS_STRING_SIZE];
256 
257     static_assert(0 == OT_HISTORY_TRACKER_ADDRESS_EVENT_ADDED, "ADDRESS_EVENT_ADDED is incorrect");
258     static_assert(1 == OT_HISTORY_TRACKER_ADDRESS_EVENT_REMOVED, "ADDRESS_EVENT_REMOVED is incorrect");
259 
260     SuccessOrExit(error = ParseArgs(aArgs, isList, numEntries));
261 
262     if (!isList)
263     {
264         // | Age                  | Event        | Multicast Address                       | Origin |
265         // +----------------------+--------------+-----------------------------------------+--------+
266 
267         static const char *const kMulticastAddrInfoTitles[] = {
268             "Age",
269             "Event",
270             "Multicast Address",
271             "Origin",
272         };
273 
274         static const uint8_t kMulticastAddrInfoColumnWidths[] = {22, 14, 42, 8};
275 
276         OutputTableHeader(kMulticastAddrInfoTitles, kMulticastAddrInfoColumnWidths);
277     }
278 
279     otHistoryTrackerInitIterator(&iterator);
280 
281     for (uint16_t index = 0; (numEntries == 0) || (index < numEntries); index++)
282     {
283         info = otHistoryTrackerIterateMulticastAddressHistory(GetInstancePtr(), &iterator, &entryAge);
284         VerifyOrExit(info != nullptr);
285 
286         otHistoryTrackerEntryAgeToString(entryAge, ageString, sizeof(ageString));
287         otIp6AddressToString(&info->mAddress, addressString, sizeof(addressString));
288 
289         OutputLine(isList ? "%s -> event:%s address:%s origin:%s" : "| %20s | %-12s | %-39s | %-6s |", ageString,
290                    Stringify(info->mEvent, kEventStrings), addressString,
291                    Interpreter::AddressOriginToString(info->mAddressOrigin));
292     }
293 
294 exit:
295     return error;
296 }
297 
298 /**
299  * @cli history neighbor
300  * @code
301  * history neighbor
302  * | Age                  | Type   | Event     | Extended Address | RLOC16 | Mode | Ave RSS |
303  * +----------------------+--------+-----------+------------------+--------+------+---------+
304  * |         00:00:29.233 | Child  | Added     | ae5105292f0b9169 | 0x8404 | -    |     -20 |
305  * |         00:01:38.368 | Child  | Removed   | ae5105292f0b9169 | 0x8401 | -    |     -20 |
306  * |         00:04:27.181 | Child  | Changed   | ae5105292f0b9169 | 0x8401 | -    |     -20 |
307  * |         00:04:51.236 | Router | Added     | 865c7ca38a5fa960 | 0x9400 | rdn  |     -20 |
308  * |         00:04:51.587 | Child  | Removed   | 865c7ca38a5fa960 | 0x8402 | rdn  |     -20 |
309  * |         00:05:22.764 | Child  | Changed   | ae5105292f0b9169 | 0x8401 | rn   |     -20 |
310  * |         00:06:40.764 | Child  | Added     | 4ec99efc874a1841 | 0x8403 | r    |     -20 |
311  * |         00:06:44.060 | Child  | Added     | 865c7ca38a5fa960 | 0x8402 | rdn  |     -20 |
312  * |         00:06:49.515 | Child  | Added     | ae5105292f0b9169 | 0x8401 | -    |     -20 |
313  * Done
314  * @endcode
315  * @code
316  * history neighbor list
317  * 00:00:34.753 -> type:Child event:Added extaddr:ae5105292f0b9169 rloc16:0x8404 mode:- rss:-20
318  * 00:01:43.888 -> type:Child event:Removed extaddr:ae5105292f0b9169 rloc16:0x8401 mode:- rss:-20
319  * 00:04:32.701 -> type:Child event:Changed extaddr:ae5105292f0b9169 rloc16:0x8401 mode:- rss:-20
320  * 00:04:56.756 -> type:Router event:Added extaddr:865c7ca38a5fa960 rloc16:0x9400 mode:rdn rss:-20
321  * 00:04:57.107 -> type:Child event:Removed extaddr:865c7ca38a5fa960 rloc16:0x8402 mode:rdn rss:-20
322  * 00:05:28.284 -> type:Child event:Changed extaddr:ae5105292f0b9169 rloc16:0x8401 mode:rn rss:-20
323  * 00:06:46.284 -> type:Child event:Added extaddr:4ec99efc874a1841 rloc16:0x8403 mode:r rss:-20
324  * 00:06:49.580 -> type:Child event:Added extaddr:865c7ca38a5fa960 rloc16:0x8402 mode:rdn rss:-20
325  * 00:06:55.035 -> type:Child event:Added extaddr:ae5105292f0b9169 rloc16:0x8401 mode:- rss:-20
326  * Done
327  * @endcode
328  * @cparam history neighbor [@ca{list}] [@ca{num-entries}]
329  * * Use the `list` option to display the output in list format. Otherwise,
330  *   the output is shown in table format.
331  * * Use the `num-entries` option to limit the output to the number of
332  *   most-recent entries specified. If this option is not used, all stored
333  *   entries are shown in the output.
334  * @par
335  * Displays the neighbor history in table or list format.
336  * @par
337  * Each table or list entry provides:
338  * * Age: Time elapsed since the command was issued, and given in the format:
339  *        `hours`:`minutes`:`seconds`:`milliseconds`
340  * * Type: `Child` or `Router`.
341  * * Event: Possible values are `Added`, `Removed`, or `Changed`.
342  * * Extended Address
343  * * RLOC16
344  * * Mode: MLE link mode. Possible values:
345  *     * `-`: no flags set (rx-off-when-idle, minimal Thread device,
346  *       stable network data).
347  *     * `r`: rx-on-when-idle
348  *     * `d`: Full Thread Device.
349  *     * `n`: Full Network Data
350  * * Ave RSS: Average number of frames (in dBm) received from the neighbor at the
351  *   time the entry was recorded.
352  * @sa otHistoryTrackerIterateNeighborHistory
353  */
Process(Arg aArgs[])354 template <> otError History::Process<Cmd("neighbor")>(Arg aArgs[])
355 {
356     static const char *const kEventString[] = {
357         /* (0) OT_HISTORY_TRACKER_NEIGHBOR_EVENT_ADDED     -> */ "Added",
358         /* (1) OT_HISTORY_TRACKER_NEIGHBOR_EVENT_REMOVED   -> */ "Removed",
359         /* (2) OT_HISTORY_TRACKER_NEIGHBOR_EVENT_CHANGED   -> */ "Changed",
360         /* (3) OT_HISTORY_TRACKER_NEIGHBOR_EVENT_RESTORING -> */ "Restoring",
361     };
362 
363     otError                             error;
364     bool                                isList;
365     uint16_t                            numEntries;
366     otHistoryTrackerIterator            iterator;
367     const otHistoryTrackerNeighborInfo *info;
368     uint32_t                            entryAge;
369     char                                ageString[OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE];
370     otLinkModeConfig                    mode;
371     char                                linkModeString[Interpreter::kLinkModeStringSize];
372 
373     static_assert(0 == OT_HISTORY_TRACKER_NEIGHBOR_EVENT_ADDED, "NEIGHBOR_EVENT_ADDED value is incorrect");
374     static_assert(1 == OT_HISTORY_TRACKER_NEIGHBOR_EVENT_REMOVED, "NEIGHBOR_EVENT_REMOVED value is incorrect");
375     static_assert(2 == OT_HISTORY_TRACKER_NEIGHBOR_EVENT_CHANGED, "NEIGHBOR_EVENT_CHANGED value is incorrect");
376     static_assert(3 == OT_HISTORY_TRACKER_NEIGHBOR_EVENT_RESTORING, "NEIGHBOR_EVENT_RESTORING value is incorrect");
377 
378     SuccessOrExit(error = ParseArgs(aArgs, isList, numEntries));
379 
380     if (!isList)
381     {
382         // | Age                  | Type   | Event     | Extended Address | RLOC16 | Mode | Ave RSS |
383         // +----------------------+--------+-----------+------------------+--------+------+---------+
384 
385         static const char *const kNeighborInfoTitles[] = {
386             "Age", "Type", "Event", "Extended Address", "RLOC16", "Mode", "Ave RSS",
387         };
388 
389         static const uint8_t kNeighborInfoColumnWidths[] = {22, 8, 11, 18, 8, 6, 9};
390 
391         OutputTableHeader(kNeighborInfoTitles, kNeighborInfoColumnWidths);
392     }
393 
394     otHistoryTrackerInitIterator(&iterator);
395 
396     for (uint16_t index = 0; (numEntries == 0) || (index < numEntries); index++)
397     {
398         info = otHistoryTrackerIterateNeighborHistory(GetInstancePtr(), &iterator, &entryAge);
399         VerifyOrExit(info != nullptr);
400 
401         otHistoryTrackerEntryAgeToString(entryAge, ageString, sizeof(ageString));
402 
403         mode.mRxOnWhenIdle = info->mRxOnWhenIdle;
404         mode.mDeviceType   = info->mFullThreadDevice;
405         mode.mNetworkData  = info->mFullNetworkData;
406         Interpreter::LinkModeToString(mode, linkModeString);
407 
408         OutputFormat(isList ? "%s -> type:%s event:%s extaddr:" : "| %20s | %-6s | %-9s | ", ageString,
409                      info->mIsChild ? "Child" : "Router", kEventString[info->mEvent]);
410         OutputExtAddress(info->mExtAddress);
411         OutputLine(isList ? " rloc16:0x%04x mode:%s rss:%d" : " | 0x%04x | %-4s | %7d |", info->mRloc16, linkModeString,
412                    info->mAverageRssi);
413     }
414 
415 exit:
416     return error;
417 }
418 
419 /**
420  * @cli history router
421  * @code
422  * history router
423  * | Age                  | Event          | ID (RLOC16) | Next Hop    | Path Cost  |
424  * +----------------------+----------------+-------------+-------------+------------+
425  * |         00:00:05.258 | NextHopChanged |  7 (0x1c00) | 34 (0x8800) | inf ->   3 |
426  * |         00:00:08.604 | NextHopChanged | 34 (0x8800) | 34 (0x8800) | inf ->   2 |
427  * |         00:00:08.604 | Added          |  7 (0x1c00) |        none | inf -> inf |
428  * |         00:00:11.931 | Added          | 34 (0x8800) |        none | inf -> inf |
429  * |         00:00:14.948 | Removed        | 59 (0xec00) |        none | inf -> inf |
430  * |         00:00:14.948 | Removed        | 54 (0xd800) |        none | inf -> inf |
431  * |         00:00:14.948 | Removed        | 34 (0x8800) |        none | inf -> inf |
432  * |         00:00:14.948 | Removed        |  7 (0x1c00) |        none | inf -> inf |
433  * |         00:00:54.795 | NextHopChanged | 59 (0xec00) | 34 (0x8800) |   1 ->   5 |
434  * |         00:02:33.735 | NextHopChanged | 54 (0xd800) |        none |  15 -> inf |
435  * |         00:03:10.915 | CostChanged    | 54 (0xd800) | 34 (0x8800) |  13 ->  15 |
436  * |         00:03:45.716 | NextHopChanged | 54 (0xd800) | 34 (0x8800) |  15 ->  13 |
437  * |         00:03:46.188 | CostChanged    | 54 (0xd800) | 59 (0xec00) |  13 ->  15 |
438  * |         00:04:19.124 | CostChanged    | 54 (0xd800) | 59 (0xec00) |  11 ->  13 |
439  * |         00:04:52.008 | CostChanged    | 54 (0xd800) | 59 (0xec00) |   9 ->  11 |
440  * |         00:05:23.176 | CostChanged    | 54 (0xd800) | 59 (0xec00) |   7 ->   9 |
441  * |         00:05:51.081 | CostChanged    | 54 (0xd800) | 59 (0xec00) |   5 ->   7 |
442  * |         00:06:48.721 | CostChanged    | 54 (0xd800) | 59 (0xec00) |   3 ->   5 |
443  * |         00:07:13.792 | NextHopChanged | 54 (0xd800) | 59 (0xec00) |   1 ->   3 |
444  * |         00:09:28.681 | NextHopChanged |  7 (0x1c00) | 34 (0x8800) | inf ->   3 |
445  * |         00:09:31.882 | Added          |  7 (0x1c00) |        none | inf -> inf |
446  * |         00:09:51.240 | NextHopChanged | 54 (0xd800) | 54 (0xd800) | inf ->   1 |
447  * |         00:09:54.204 | Added          | 54 (0xd800) |        none | inf -> inf |
448  * |         00:10:20.645 | NextHopChanged | 34 (0x8800) | 34 (0x8800) | inf ->   2 |
449  * |         00:10:24.242 | NextHopChanged | 59 (0xec00) | 59 (0xec00) | inf ->   1 |
450  * |         00:10:24.242 | Added          | 34 (0x8800) |        none | inf -> inf |
451  * |         00:10:41.900 | NextHopChanged | 59 (0xec00) |        none |   1 -> inf |
452  * |         00:10:42.480 | Added          |  3 (0x0c00) |  3 (0x0c00) | inf -> inf |
453  * |         00:10:43.614 | Added          | 59 (0xec00) | 59 (0xec00) | inf ->   1 |
454  * Done
455  * @endcode
456  * @code
457  * history router list 20
458  * 00:00:06.959 -> event:NextHopChanged router:7(0x1c00) nexthop:34(0x8800) old-cost:inf new-cost:3
459  * 00:00:10.305 -> event:NextHopChanged router:34(0x8800) nexthop:34(0x8800) old-cost:inf new-cost:2
460  * 00:00:10.305 -> event:Added router:7(0x1c00) nexthop:none old-cost:inf new-cost:inf
461  * 00:00:13.632 -> event:Added router:34(0x8800) nexthop:none old-cost:inf new-cost:inf
462  * 00:00:16.649 -> event:Removed router:59(0xec00) nexthop:none old-cost:inf new-cost:inf
463  * 00:00:16.649 -> event:Removed router:54(0xd800) nexthop:none old-cost:inf new-cost:inf
464  * 00:00:16.649 -> event:Removed router:34(0x8800) nexthop:none old-cost:inf new-cost:inf
465  * 00:00:16.649 -> event:Removed router:7(0x1c00) nexthop:none old-cost:inf new-cost:inf
466  * 00:00:56.496 -> event:NextHopChanged router:59(0xec00) nexthop:34(0x8800) old-cost:1 new-cost:5
467  * 00:02:35.436 -> event:NextHopChanged router:54(0xd800) nexthop:none old-cost:15 new-cost:inf
468  * 00:03:12.616 -> event:CostChanged router:54(0xd800) nexthop:34(0x8800) old-cost:13 new-cost:15
469  * 00:03:47.417 -> event:NextHopChanged router:54(0xd800) nexthop:34(0x8800) old-cost:15 new-cost:13
470  * 00:03:47.889 -> event:CostChanged router:54(0xd800) nexthop:59(0xec00) old-cost:13 new-cost:15
471  * 00:04:20.825 -> event:CostChanged router:54(0xd800) nexthop:59(0xec00) old-cost:11 new-cost:13
472  * 00:04:53.709 -> event:CostChanged router:54(0xd800) nexthop:59(0xec00) old-cost:9 new-cost:11
473  * 00:05:24.877 -> event:CostChanged router:54(0xd800) nexthop:59(0xec00) old-cost:7 new-cost:9
474  * 00:05:52.782 -> event:CostChanged router:54(0xd800) nexthop:59(0xec00) old-cost:5 new-cost:7
475  * 00:06:50.422 -> event:CostChanged router:54(0xd800) nexthop:59(0xec00) old-cost:3 new-cost:5
476  * 00:07:15.493 -> event:NextHopChanged router:54(0xd800) nexthop:59(0xec00) old-cost:1 new-cost:3
477  * 00:09:30.382 -> event:NextHopChanged router:7(0x1c00) nexthop:34(0x8800) old-cost:inf new-cost:3
478  * Done
479  * @endcode
480  * @cparam history router [@ca{list}] [@ca{num-entries}]
481  * * Use the `list` option to display the output in list format. Otherwise,
482  *   the output is shown in table format.
483  * * Use the `num-entries` option to limit the output to the number of
484  *   most-recent entries specified. If this option is not used, all stored
485  *   entries are shown in the output.
486  * @par
487  * Displays the route-table history in table or list format.
488  * @par
489  * Each table or list entry provides:
490  * * Age: Time elapsed since the command was issued, and given in the format:
491  *        `hours`:`minutes`:`seconds`:`milliseconds`
492  * * Event: Possible values are `Added`, `Removed`, `NextHopChanged`, or `CostChanged`.
493  * * ID (RLOC16): Router ID and RLOC16 of the router.
494  * * Next Hop: Router ID and RLOC16 of the next hop. If there is no next hop,
495  *             `none` is shown.
496  * * Path Cost: old cost `->` new cost. A value of `inf` indicates an infinite
497  *      	path cost.
498  * @sa otHistoryTrackerIterateRouterHistory
499  */
Process(Arg aArgs[])500 template <> otError History::Process<Cmd("router")>(Arg aArgs[])
501 {
502     static const char *const kEventString[] = {
503         /* (0) OT_HISTORY_TRACKER_ROUTER_EVENT_ADDED             -> */ "Added",
504         /* (1) OT_HISTORY_TRACKER_ROUTER_EVENT_REMOVED           -> */ "Removed",
505         /* (2) OT_HISTORY_TRACKER_ROUTER_EVENT_NEXT_HOP_CHANGED  -> */ "NextHopChanged",
506         /* (3) OT_HISTORY_TRACKER_ROUTER_EVENT_COST_CHANGED      -> */ "CostChanged",
507     };
508 
509     constexpr uint8_t kRouterIdOffset = 10; // Bit offset of Router ID in RLOC16
510 
511     otError                           error;
512     bool                              isList;
513     uint16_t                          numEntries;
514     otHistoryTrackerIterator          iterator;
515     const otHistoryTrackerRouterInfo *info;
516     uint32_t                          entryAge;
517     char                              ageString[OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE];
518 
519     static_assert(0 == OT_HISTORY_TRACKER_ROUTER_EVENT_ADDED, "EVENT_ADDED is incorrect");
520     static_assert(1 == OT_HISTORY_TRACKER_ROUTER_EVENT_REMOVED, "EVENT_REMOVED is incorrect");
521     static_assert(2 == OT_HISTORY_TRACKER_ROUTER_EVENT_NEXT_HOP_CHANGED, "EVENT_NEXT_HOP_CHANGED is incorrect");
522     static_assert(3 == OT_HISTORY_TRACKER_ROUTER_EVENT_COST_CHANGED, "EVENT_COST_CHANGED is incorrect");
523 
524     SuccessOrExit(error = ParseArgs(aArgs, isList, numEntries));
525 
526     if (!isList)
527     {
528         // | Age                  | Event          | ID (RlOC16) | Next Hop   | Path Cost   |
529         // +----------------------+----------------+-------------+------------+-------------+
530 
531         static const char *const kRouterInfoTitles[] = {
532             "Age", "Event", "ID (RLOC16)", "Next Hop", "Path Cost",
533         };
534 
535         static const uint8_t kRouterInfoColumnWidths[] = {22, 16, 13, 13, 12};
536 
537         OutputTableHeader(kRouterInfoTitles, kRouterInfoColumnWidths);
538     }
539 
540     otHistoryTrackerInitIterator(&iterator);
541 
542     for (uint16_t index = 0; (numEntries == 0) || (index < numEntries); index++)
543     {
544         info = otHistoryTrackerIterateRouterHistory(GetInstancePtr(), &iterator, &entryAge);
545         VerifyOrExit(info != nullptr);
546 
547         otHistoryTrackerEntryAgeToString(entryAge, ageString, sizeof(ageString));
548 
549         OutputFormat(isList ? "%s -> event:%s router:%u(0x%04x) nexthop:" : "| %20s | %-14s | %2u (0x%04x) | ",
550                      ageString, kEventString[info->mEvent], info->mRouterId,
551                      static_cast<uint16_t>(info->mRouterId) << kRouterIdOffset);
552 
553         if (info->mNextHop != OT_HISTORY_TRACKER_NO_NEXT_HOP)
554         {
555             OutputFormat(isList ? "%u(0x%04x)" : "%2u (0x%04x)", info->mNextHop,
556                          static_cast<uint16_t>(info->mNextHop) << kRouterIdOffset);
557         }
558         else
559         {
560             OutputFormat(isList ? "%s" : "%11s", "none");
561         }
562 
563         if (info->mOldPathCost != OT_HISTORY_TRACKER_INFINITE_PATH_COST)
564         {
565             OutputFormat(isList ? " old-cost:%u" : " | %3u ->", info->mOldPathCost);
566         }
567         else
568         {
569             OutputFormat(isList ? " old-cost:inf" : " | inf ->");
570         }
571 
572         if (info->mPathCost != OT_HISTORY_TRACKER_INFINITE_PATH_COST)
573         {
574             OutputLine(isList ? " new-cost:%u" : " %3u |", info->mPathCost);
575         }
576         else
577         {
578             OutputLine(isList ? " new-cost:inf" : " inf |");
579         }
580     }
581 
582 exit:
583     return error;
584 }
585 
586 /**
587  * @cli history netinfo
588  * @code
589  * history netinfo
590  * | Age                  | Role     | Mode | RLOC16 | Partition ID |
591  * +----------------------+----------+------+--------+--------------+
592  * |         00:00:10.069 | router   | rdn  | 0x6000 |    151029327 |
593  * |         00:02:09.337 | child    | rdn  | 0x2001 |    151029327 |
594  * |         00:02:09.338 | child    | rdn  | 0x2001 |    151029327 |
595  * |         00:07:40.806 | child    | -    | 0x2001 |    151029327 |
596  * |         00:07:42.297 | detached | -    | 0x6000 |            0 |
597  * |         00:07:42.968 | disabled | -    | 0x6000 |            0 |
598  * Done
599  * @endcode
600  * @code
601  * history netinfo list
602  * 00:00:59.467 -> role:router mode:rdn rloc16:0x6000 partition-id:151029327
603  * 00:02:58.735 -> role:child mode:rdn rloc16:0x2001 partition-id:151029327
604  * 00:02:58.736 -> role:child mode:rdn rloc16:0x2001 partition-id:151029327
605  * 00:08:30.204 -> role:child mode:- rloc16:0x2001 partition-id:151029327
606  * 00:08:31.695 -> role:detached mode:- rloc16:0x6000 partition-id:0
607  * 00:08:32.366 -> role:disabled mode:- rloc16:0x6000 partition-id:0
608  * Done
609  * @endcode
610  * @code
611  * history netinfo 2
612  * | Age                  | Role     | Mode | RLOC16 | Partition ID |
613  * +----------------------+----------+------+--------+--------------+
614  * |         00:02:05.451 | router   | rdn  | 0x6000 |    151029327 |
615  * |         00:04:04.719 | child    | rdn  | 0x2001 |    151029327 |
616  * Done
617  * @endcode
618  * @cparam history netinfo [@ca{list}] [@ca{num-entries}]
619  * * Use the `list` option to display the output in list format. Otherwise,
620  *   the output is shown in table format.
621  * * Use the `num-entries` option to limit the output to the number of
622  *   most-recent entries specified. If this option is not used, all stored
623  *   entries are shown in the output.
624  * @par
625  * Displays the network info history in table or list format.
626  * @par
627  * Each table or list entry provides:
628  * * Age: Time elapsed since the command was issued, and given in the format:
629  *        `hours`:`minutes`:`seconds`:`milliseconds`
630  * * Role: Device role. Possible values are `router`, `child`, `detached`, or `disabled`.
631  * * Mode: MLE link mode. Possible values:
632  *     * `-`: no flags set (rx-off-when-idle, minimal Thread device,
633  *       stable network data).
634  *     * `r`: rx-on-when-idle
635  *     * `d`: Full Thread Device.
636  *     * `n`: Full Network Data
637  * * RLOC16
638  * * Partition ID.
639  * @sa otHistoryTrackerIterateNetInfoHistory
640  */
Process(Arg aArgs[])641 template <> otError History::Process<Cmd("netinfo")>(Arg aArgs[])
642 {
643     otError                            error;
644     bool                               isList;
645     uint16_t                           numEntries;
646     otHistoryTrackerIterator           iterator;
647     const otHistoryTrackerNetworkInfo *info;
648     uint32_t                           entryAge;
649     char                               ageString[OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE];
650     char                               linkModeString[Interpreter::kLinkModeStringSize];
651 
652     SuccessOrExit(error = ParseArgs(aArgs, isList, numEntries));
653 
654     if (!isList)
655     {
656         // | Age                  | Role     | Mode | RLOC16 | Partition ID |
657         // +----------------------+----------+------+--------+--------------+
658 
659         static const char *const kNetInfoTitles[]       = {"Age", "Role", "Mode", "RLOC16", "Partition ID"};
660         static const uint8_t     kNetInfoColumnWidths[] = {22, 10, 6, 8, 14};
661 
662         OutputTableHeader(kNetInfoTitles, kNetInfoColumnWidths);
663     }
664 
665     otHistoryTrackerInitIterator(&iterator);
666 
667     for (uint16_t index = 0; (numEntries == 0) || (index < numEntries); index++)
668     {
669         info = otHistoryTrackerIterateNetInfoHistory(GetInstancePtr(), &iterator, &entryAge);
670         VerifyOrExit(info != nullptr);
671 
672         otHistoryTrackerEntryAgeToString(entryAge, ageString, sizeof(ageString));
673 
674         OutputLine(
675             isList ? "%s -> role:%s mode:%s rloc16:0x%04x partition-id:%lu" : "| %20s | %-8s | %-4s | 0x%04x | %12lu |",
676             ageString, otThreadDeviceRoleToString(info->mRole),
677             Interpreter::LinkModeToString(info->mMode, linkModeString), info->mRloc16, ToUlong(info->mPartitionId));
678     }
679 
680 exit:
681     return error;
682 }
683 
684 /**
685  * @cli history rx
686  * @code
687  * history rx
688  * | Age                  | Type             | Len   | Chksum | Sec | Prio | RSS  |Dir | Neighb | Radio |
689  * +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
690  * |                      | UDP              |    50 | 0xbd26 |  no |  net |  -20 | RX | 0x4800 |  15.4 |
691  * |         00:00:07.640 | src: [fe80:0:0:0:d03d:d3e7:cc5e:7cd7]:19788                                 |
692  * |                      | dst: [ff02:0:0:0:0:0:0:1]:19788                                             |
693  * +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
694  * |                      | HopOpts          |    44 | 0x0000 | yes | norm |  -20 | RX | 0x4800 |  15.4 |
695  * |         00:00:09.263 | src: [fdde:ad00:beef:0:0:ff:fe00:4800]:0                                    |
696  * |                      | dst: [ff03:0:0:0:0:0:0:2]:0                                                 |
697  * +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
698  * |                      | UDP              |    12 | 0x3f7d | yes |  net |  -20 | RX | 0x4800 |  15.4 |
699  * |         00:00:09.302 | src: [fdde:ad00:beef:0:0:ff:fe00:4800]:61631                                |
700  * |                      | dst: [fdde:ad00:beef:0:0:ff:fe00:4801]:61631                                |
701  * +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
702  * |                      | ICMP6(EchoReqst) |    16 | 0x942c | yes | norm |  -20 | RX | 0x4800 |  15.4 |
703  * |         00:00:09.304 | src: [fdde:ad00:beef:0:ac09:a16b:3204:dc09]:0                               |
704  * |                      | dst: [fdde:ad00:beef:0:dc0e:d6b3:f180:b75b]:0                               |
705  * +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
706  * |                      | HopOpts          |    44 | 0x0000 | yes | norm |  -20 | RX | 0x4800 |  15.4 |
707  * |         00:00:09.304 | src: [fdde:ad00:beef:0:0:ff:fe00:4800]:0                                    |
708  * |                      | dst: [ff03:0:0:0:0:0:0:2]:0                                                 |
709  * +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
710  * |                      | UDP              |    50 | 0x2e37 |  no |  net |  -20 | RX | 0x4800 |  15.4 |
711  * |         00:00:21.622 | src: [fe80:0:0:0:d03d:d3e7:cc5e:7cd7]:19788                                 |
712  * |                      | dst: [ff02:0:0:0:0:0:0:1]:19788                                             |
713  * +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
714  * |                      | UDP              |    50 | 0xe177 |  no |  net |  -20 | RX | 0x4800 |  15.4 |
715  * |         00:00:26.640 | src: [fe80:0:0:0:d03d:d3e7:cc5e:7cd7]:19788                                 |
716  * |                      | dst: [ff02:0:0:0:0:0:0:1]:19788                                             |
717  * +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
718  * |                      | UDP              |   165 | 0x82ee | yes |  net |  -20 | RX | 0x4800 |  15.4 |
719  * |         00:00:30.000 | src: [fe80:0:0:0:d03d:d3e7:cc5e:7cd7]:19788                                 |
720  * |                      | dst: [fe80:0:0:0:a4a5:bbac:a8e:bd07]:19788                                  |
721  * +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
722  * |                      | UDP              |    93 | 0x52df |  no |  net |  -20 | RX | unknwn |  15.4 |
723  * |         00:00:30.480 | src: [fe80:0:0:0:d03d:d3e7:cc5e:7cd7]:19788                                 |
724  * |                      | dst: [fe80:0:0:0:a4a5:bbac:a8e:bd07]:19788                                  |
725  * +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
726  * |                      | UDP              |    50 | 0x5ccf |  no |  net |  -20 | RX | unknwn |  15.4 |
727  * |         00:00:30.772 | src: [fe80:0:0:0:d03d:d3e7:cc5e:7cd7]:19788                                 |
728  * |                      | dst: [ff02:0:0:0:0:0:0:1]:19788                                             |
729  * Done
730  * @endcode
731  * @code
732  * history rx list 4
733  * 00:00:13.368
734        type:UDP len:50 checksum:0xbd26 sec:no prio:net rss:-20 from:0x4800 radio:15.4
735        src:[fe80:0:0:0:d03d:d3e7:cc5e:7cd7]:19788
736        dst:[ff02:0:0:0:0:0:0:1]:19788
737  * 00:00:14.991
738        type:HopOpts len:44 checksum:0x0000 sec:yes prio:norm rss:-20 from:0x4800 radio:15.4
739        src:[fdde:ad00:beef:0:0:ff:fe00:4800]:0
740        dst:[ff03:0:0:0:0:0:0:2]:0
741  * 00:00:15.030
742        type:UDP len:12 checksum:0x3f7d sec:yes prio:net rss:-20 from:0x4800 radio:15.4
743        src:[fdde:ad00:beef:0:0:ff:fe00:4800]:61631
744        dst:[fdde:ad00:beef:0:0:ff:fe00:4801]:61631
745  * 00:00:15.032
746        type:ICMP6(EchoReqst) len:16 checksum:0x942c sec:yes prio:norm rss:-20 from:0x4800 radio:15.4
747        src:[fdde:ad00:beef:0:ac09:a16b:3204:dc09]:0
748        dst:[fdde:ad00:beef:0:dc0e:d6b3:f180:b75b]:0
749  * Done
750  * @endcode
751  * @cparam history rx [@ca{list}] [@ca{num-entries}]
752  * * Use the `list` option to display the output in list format. Otherwise,
753  *   the output is shown in table format.
754  * * Use the `num-entries` option to limit the output to the number of
755  *   most-recent entries specified. If this option is not used, all stored
756  *   entries are shown in the output.
757  * @par
758  * Displays the IPv6 message RX history in table or list format.
759  * @par
760  * Each table or list entry provides:
761  * * Age: Time elapsed since the command was issued, and given in the format:
762  *        `hours`:`minutes`:`seconds`:`milliseconds`
763  * * Type:
764  *     * IPv6 message type, such as `UDP`, `TCP`, `HopOpts`, and `ICMP6` (and its subtype).
765  *     * `src`: Source IPv6 address and port number.
766  *     * `dst`: Destination IPv6 address and port number (port number is valid
767          for UDP/TCP, otherwise it is 0).
768  * * Len: IPv6 payload length (excluding the IPv6 header).
769  * * Chksum: Message checksum (valid for UDP, TCP, or ICMP6 messages).
770  * * Sec: Indicates if link-layer security was used.
771  * * Prio: Message priority. Possible values are `low`, `norm`, `high`, or
772  *         `net` (for Thread control messages).
773  * * RSS: Received Signal Strength (in dBm), averaged over all received fragment
774  *        frames that formed the message. For TX history, `NA` (not applicable)
775           is displayed.
776  * * Dir: Shows whether the message was sent (`TX`) or received (`RX`). A failed
777  *        transmission is indicated with `TX-F` in table format or
778  *        `tx-success:no` in list format. Examples of a failed transmission
779  *        include a `tx`getting aborted and no `ack` getting sent from the peer for
780  *        any of the message fragments.
781  * * Neighb: Short address (RLOC16) of the neighbor with whom the message was
782  *           sent/received. If the frame was broadcast, it is shown as
783  *           `bcast` in table format or `0xffff` in list format. If the short
784  *           address of the neighbor is not available, it is shown as `unknwn` in
785  *           table format or `0xfffe` in list format.
786  * * Radio: Radio link on which the message was sent/received (useful when
787             `OPENTHREAD_CONFIG_MULTI_RADIO` is enabled). Can be `15.4`, `trel`,
788             or `all` (if sent on all radio links).
789  * @sa otHistoryTrackerIterateRxHistory
790  */
Process(Arg aArgs[])791 template <> otError History::Process<Cmd("rx")>(Arg aArgs[]) { return ProcessRxTxHistory(kRx, aArgs); }
792 
793 /**
794  * @cli history rxtx
795  * @code
796  * history rxtx
797  * | Age                  | Type             | Len   | Chksum | Sec | Prio | RSS  |Dir | Neighb | Radio |
798  * +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
799  * |                      | HopOpts          |    44 | 0x0000 | yes | norm |  -20 | RX | 0x0800 |  15.4 |
800  * |         00:00:09.267 | src: [fdde:ad00:beef:0:0:ff:fe00:800]:0                                     |
801  * |                      | dst: [ff03:0:0:0:0:0:0:2]:0                                                 |
802  * +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
803  * |                      | UDP              |    12 | 0x6c6b | yes |  net |  -20 | RX | 0x0800 |  15.4 |
804  * |         00:00:09.290 | src: [fdde:ad00:beef:0:0:ff:fe00:800]:61631                                 |
805  * |                      | dst: [fdde:ad00:beef:0:0:ff:fe00:801]:61631                                 |
806  * +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
807  * |                      | ICMP6(EchoReqst) |    16 | 0xc6a2 | yes | norm |  -20 | RX | 0x0800 |  15.4 |
808  * |         00:00:09.292 | src: [fdde:ad00:beef:0:efe8:4910:cf95:dee9]:0                               |
809  * |                      | dst: [fdde:ad00:beef:0:af4c:3644:882a:3698]:0                               |
810  * +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
811  * |                      | ICMP6(EchoReply) |    16 | 0xc5a2 | yes | norm |  NA  | TX | 0x0800 |  15.4 |
812  * |         00:00:09.292 | src: [fdde:ad00:beef:0:af4c:3644:882a:3698]:0                               |
813  * |                      | dst: [fdde:ad00:beef:0:efe8:4910:cf95:dee9]:0                               |
814  * +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
815  * |                      | UDP              |    50 | 0xaa0d | yes |  net |  NA  | TX | 0x0800 |  15.4 |
816  * |         00:00:09.294 | src: [fdde:ad00:beef:0:0:ff:fe00:801]:61631                                 |
817  * |                      | dst: [fdde:ad00:beef:0:0:ff:fe00:800]:61631                                 |
818  * +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
819  * |                      | HopOpts          |    44 | 0x0000 | yes | norm |  -20 | RX | 0x0800 |  15.4 |
820  * |         00:00:09.296 | src: [fdde:ad00:beef:0:0:ff:fe00:800]:0                                     |
821  * |                      | dst: [ff03:0:0:0:0:0:0:2]:0                                                 |
822  * +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
823  * |                      | UDP              |    50 | 0xc1d8 |  no |  net |  -20 | RX | 0x0800 |  15.4 |
824  * |         00:00:09.569 | src: [fe80:0:0:0:54d9:5153:ffc6:df26]:19788                                 |
825  * |                      | dst: [ff02:0:0:0:0:0:0:1]:19788                                             |
826  * +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
827  * |                      | UDP              |    50 | 0x3cb1 |  no |  net |  -20 | RX | 0x0800 |  15.4 |
828  * |         00:00:16.519 | src: [fe80:0:0:0:54d9:5153:ffc6:df26]:19788                                 |
829  * |                      | dst: [ff02:0:0:0:0:0:0:1]:19788                                             |
830  * +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
831  * |                      | UDP              |    50 | 0xeda0 |  no |  net |  -20 | RX | 0x0800 |  15.4 |
832  * |         00:00:20.599 | src: [fe80:0:0:0:54d9:5153:ffc6:df26]:19788                                 |
833  * |                      | dst: [ff02:0:0:0:0:0:0:1]:19788                                             |
834  * +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
835  * |                      | UDP              |   165 | 0xbdfa | yes |  net |  -20 | RX | 0x0800 |  15.4 |
836  * |         00:00:21.059 | src: [fe80:0:0:0:54d9:5153:ffc6:df26]:19788                                 |
837  * |                      | dst: [fe80:0:0:0:8893:c2cc:d983:1e1c]:19788                                 |
838  * +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
839  * |                      | UDP              |    64 | 0x1c11 |  no |  net |  NA  | TX | 0x0800 |  15.4 |
840  * |         00:00:21.062 | src: [fe80:0:0:0:8893:c2cc:d983:1e1c]:19788                                 |
841  * |                      | dst: [fe80:0:0:0:54d9:5153:ffc6:df26]:19788                                 |
842  * +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
843  * |                      | UDP              |    93 | 0xedff |  no |  net |  -20 | RX | unknwn |  15.4 |
844  * |         00:00:21.474 | src: [fe80:0:0:0:54d9:5153:ffc6:df26]:19788                                 |
845  * |                      | dst: [fe80:0:0:0:8893:c2cc:d983:1e1c]:19788                                 |
846  * +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
847  * |                      | UDP              |    44 | 0xd383 |  no |  net |  NA  | TX | bcast  |  15.4 |
848  * |         00:00:21.811 | src: [fe80:0:0:0:8893:c2cc:d983:1e1c]:19788                                 |
849  * |                      | dst: [ff02:0:0:0:0:0:0:2]:19788                                             |
850  * Done
851  * @endcode
852  * @code
853  * history rxtx list 5
854  * 00:00:02.100
855        type:UDP len:50 checksum:0xd843 sec:no prio:net rss:-20 from:0x0800 radio:15.4
856        src:[fe80:0:0:0:54d9:5153:ffc6:df26]:19788
857        dst:[ff02:0:0:0:0:0:0:1]:19788
858  * 00:00:15.331
859        type:HopOpts len:44 checksum:0x0000 sec:yes prio:norm rss:-20 from:0x0800 radio:15.4
860        src:[fdde:ad00:beef:0:0:ff:fe00:800]:0
861        dst:[ff03:0:0:0:0:0:0:2]:0
862  * 00:00:15.354
863        type:UDP len:12 checksum:0x6c6b sec:yes prio:net rss:-20 from:0x0800 radio:15.4
864        src:[fdde:ad00:beef:0:0:ff:fe00:800]:61631
865        dst:[fdde:ad00:beef:0:0:ff:fe00:801]:61631
866  * 00:00:15.356
867        type:ICMP6(EchoReqst) len:16 checksum:0xc6a2 sec:yes prio:norm rss:-20 from:0x0800 radio:15.4
868        src:[fdde:ad00:beef:0:efe8:4910:cf95:dee9]:0
869        dst:[fdde:ad00:beef:0:af4c:3644:882a:3698]:0
870  * 00:00:15.356
871        type:ICMP6(EchoReply) len:16 checksum:0xc5a2 sec:yes prio:norm tx-success:yes to:0x0800 radio:15.4
872        src:[fdde:ad00:beef:0:af4c:3644:882a:3698]:0
873        dst:[fdde:ad00:beef:0:efe8:4910:cf95:dee9]:0
874  * Done
875  * @endcode
876  * @cparam history rxtx [@ca{list}] [@ca{num-entries}]
877  * * Use the `list` option to display the output in list format. Otherwise,
878  *   the output is shown in table format.
879  * * Use the `num-entries` option to limit the output to the number of
880  *   most-recent entries specified. If this option is not used, all stored
881  *   entries are shown in the output.
882  * @par
883  * Displays the combined IPv6 message RX and TX history in table or list format.
884  * @par
885  * Each table or list entry provides:
886  * * Age: Time elapsed since the command was issued, and given in the format:
887  *        `hours`:`minutes`:`seconds`:`milliseconds`
888  * * Type:
889  *     * IPv6 message type, such as `UDP`, `TCP`, `HopOpts`, and `ICMP6` (and its subtype).
890  *     * `src`: Source IPv6 address and port number.
891  *     * `dst`: Destination IPv6 address and port number (port number is valid
892          for UDP/TCP, otherwise it is 0).
893  * * Len: IPv6 payload length (excluding the IPv6 header).
894  * * Chksum: Message checksum (valid for UDP, TCP, or ICMP6 messages).
895  * * Sec: Indicates if link-layer security was used.
896  * * Prio: Message priority. Possible values are `low`, `norm`, `high`, or
897  *         `net` (for Thread control messages).
898  * * RSS: Received Signal Strength (in dBm), averaged over all received fragment
899  *        frames that formed the message. For TX history, `NA` (not applicable)
900           is displayed.
901  * * Dir: Shows whether the message was sent (`TX`) or received (`RX`). A failed
902  *        transmission is indicated with `TX-F` in table format or
903  *        `tx-success:no` in list format. Examples of a failed transmission
904  *        include a `tx`getting aborted and no `ack` getting sent from the peer for
905  *        any of the message fragments.
906  * * Neighb: Short address (RLOC16) of the neighbor with whom the message was
907  *           sent/received. If the frame was broadcast, it is shown as
908  *           `bcast` in table format or `0xffff` in list format. If the short
909  *           address of the neighbor is not available, it is shown as `unknwn` in
910  *           table format or `0xfffe` in list format.
911  * * Radio: Radio link on which the message was sent/received (useful when
912             `OPENTHREAD_CONFIG_MULTI_RADIO` is enabled). Can be `15.4`, `trel`,
913             or `all` (if sent on all radio links).
914  * @sa otHistoryTrackerIterateRxHistory
915  * @sa otHistoryTrackerIterateTxHistory
916  */
Process(Arg aArgs[])917 template <> otError History::Process<Cmd("rxtx")>(Arg aArgs[]) { return ProcessRxTxHistory(kRxTx, aArgs); }
918 
919 /**
920  * @cli history tx
921  * @code
922  * history tx
923  * | Age                  | Type             | Len   | Chksum | Sec | Prio | RSS  |Dir | Neighb | Radio |
924  * +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
925  * |                      | ICMP6(EchoReply) |    16 | 0x932c | yes | norm |  NA  | TX | 0x4800 |  15.4 |
926  * |         00:00:18.798 | src: [fdde:ad00:beef:0:dc0e:d6b3:f180:b75b]:0                               |
927  * |                      | dst: [fdde:ad00:beef:0:ac09:a16b:3204:dc09]:0                               |
928  * +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
929  * |                      | UDP              |    50 | 0xce87 | yes |  net |  NA  | TX | 0x4800 |  15.4 |
930  * |         00:00:18.800 | src: [fdde:ad00:beef:0:0:ff:fe00:4801]:61631                                |
931  * |                      | dst: [fdde:ad00:beef:0:0:ff:fe00:4800]:61631                                |
932  * +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
933  * |                      | UDP              |    64 | 0xf7ba |  no |  net |  NA  | TX | 0x4800 |  15.4 |
934  * |         00:00:39.499 | src: [fe80:0:0:0:a4a5:bbac:a8e:bd07]:19788                                  |
935  * |                      | dst: [fe80:0:0:0:d03d:d3e7:cc5e:7cd7]:19788                                 |
936  * +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
937  * |                      | UDP              |    44 | 0x26d4 |  no |  net |  NA  | TX | bcast  |  15.4 |
938  * |         00:00:40.256 | src: [fe80:0:0:0:a4a5:bbac:a8e:bd07]:19788                                  |
939  * |                      | dst: [ff02:0:0:0:0:0:0:2]:19788                                             |
940  * Done
941  * @endcode
942  * @code
943  * history tx list
944  * 00:00:23.957
945        type:ICMP6(EchoReply) len:16 checksum:0x932c sec:yes prio:norm tx-success:yes to:0x4800 radio:15.4
946        src:[fdde:ad00:beef:0:dc0e:d6b3:f180:b75b]:0
947        dst:[fdde:ad00:beef:0:ac09:a16b:3204:dc09]:0
948  * 00:00:23.959
949        type:UDP len:50 checksum:0xce87 sec:yes prio:net tx-success:yes to:0x4800 radio:15.4
950        src:[fdde:ad00:beef:0:0:ff:fe00:4801]:61631
951        dst:[fdde:ad00:beef:0:0:ff:fe00:4800]:61631
952  * 00:00:44.658
953        type:UDP len:64 checksum:0xf7ba sec:no prio:net tx-success:yes to:0x4800 radio:15.4
954        src:[fe80:0:0:0:a4a5:bbac:a8e:bd07]:19788
955        dst:[fe80:0:0:0:d03d:d3e7:cc5e:7cd7]:19788
956  * 00:00:45.415
957        type:UDP len:44 checksum:0x26d4 sec:no prio:net tx-success:yes to:0xffff radio:15.4
958        src:[fe80:0:0:0:a4a5:bbac:a8e:bd07]:19788
959        dst:[ff02:0:0:0:0:0:0:2]:19788
960  * Done
961  * @endcode
962  * @cparam history tx [@ca{list}] [@ca{num-entries}]
963  * * Use the `list` option to display the output in list format. Otherwise,
964  *   the output is shown in table format.
965  * * Use the `num-entries` option to limit the output to the number of
966  *   most-recent entries specified. If this option is not used, all stored
967  *   entries are shown in the output.
968  * @par
969  * Displays the IPv6 message TX history in table or list format.
970  * @par
971  * Each table or list entry provides:
972  * * Age: Time elapsed since the command was issued, and given in the format:
973  *        `hours`:`minutes`:`seconds`:`milliseconds`
974  * * Type:
975  *     * IPv6 message type, such as `UDP`, `TCP`, `HopOpts`, and `ICMP6` (and its subtype).
976  *     * `src`: Source IPv6 address and port number.
977  *     * `dst`: Destination IPv6 address and port number (port number is valid
978          for UDP/TCP, otherwise it is 0).
979  * * Len: IPv6 payload length (excluding the IPv6 header).
980  * * Chksum: Message checksum (valid for UDP, TCP, or ICMP6 messages).
981  * * Sec: Indicates if link-layer security was used.
982  * * Prio: Message priority. Possible values are `low`, `norm`, `high`, or
983  *         `net` (for Thread control messages).
984  * * RSS: Received Signal Strength (in dBm), averaged over all received fragment
985  *        frames that formed the message. For TX history, `NA` (not applicable)
986           is displayed.
987  * * Dir: Shows whether the message was sent (`TX`) or received (`RX`). A failed
988  *        transmission is indicated with `TX-F` in table format or
989  *        `tx-success:no` in list format. Examples of a failed transmission
990  *        include a `tx`getting aborted and no `ack` getting sent from the peer for
991  *        any of the message fragments.
992  * * Neighb: Short address (RLOC16) of the neighbor with whom the message was
993  *           sent/received. If the frame was broadcast, it is shown as
994  *           `bcast` in table format or `0xffff` in list format. If the short
995  *           address of the neighbor is not available, it is shown as `unknwn` in
996  *           table format or `0xfffe` in list format.
997  * * Radio: Radio link on which the message was sent/received (useful when
998             `OPENTHREAD_CONFIG_MULTI_RADIO` is enabled). Can be `15.4`, `trel`,
999             or `all` (if sent on all radio links).
1000  * @sa otHistoryTrackerIterateTxHistory
1001  */
Process(Arg aArgs[])1002 template <> otError History::Process<Cmd("tx")>(Arg aArgs[]) { return ProcessRxTxHistory(kTx, aArgs); }
1003 
MessagePriorityToString(uint8_t aPriority)1004 const char *History::MessagePriorityToString(uint8_t aPriority)
1005 {
1006     static const char *const kPriorityStrings[] = {
1007         "low",  // (0) OT_HISTORY_TRACKER_MSG_PRIORITY_LOW
1008         "norm", // (1) OT_HISTORY_TRACKER_MSG_PRIORITY_NORMAL
1009         "high", // (2) OT_HISTORY_TRACKER_MSG_PRIORITY_HIGH
1010         "net",  // (3) OT_HISTORY_TRACKER_MSG_PRIORITY_NET
1011     };
1012 
1013     static_assert(0 == OT_HISTORY_TRACKER_MSG_PRIORITY_LOW, "MSG_PRIORITY_LOW value is incorrect");
1014     static_assert(1 == OT_HISTORY_TRACKER_MSG_PRIORITY_NORMAL, "MSG_PRIORITY_NORMAL value is incorrect");
1015     static_assert(2 == OT_HISTORY_TRACKER_MSG_PRIORITY_HIGH, "MSG_PRIORITY_HIGH value is incorrect");
1016     static_assert(3 == OT_HISTORY_TRACKER_MSG_PRIORITY_NET, "MSG_PRIORITY_NET value is incorrect");
1017 
1018     return Stringify(aPriority, kPriorityStrings, "unkn");
1019 }
1020 
RadioTypeToString(const otHistoryTrackerMessageInfo & aInfo)1021 const char *History::RadioTypeToString(const otHistoryTrackerMessageInfo &aInfo)
1022 {
1023     const char *str = "none";
1024 
1025     if (aInfo.mRadioTrelUdp6 && aInfo.mRadioIeee802154)
1026     {
1027         str = "all";
1028     }
1029     else if (aInfo.mRadioIeee802154)
1030     {
1031         str = "15.4";
1032     }
1033     else if (aInfo.mRadioTrelUdp6)
1034     {
1035         str = "trel";
1036     }
1037 
1038     return str;
1039 }
1040 
MessageTypeToString(const otHistoryTrackerMessageInfo & aInfo)1041 const char *History::MessageTypeToString(const otHistoryTrackerMessageInfo &aInfo)
1042 {
1043     const char *str = otIp6ProtoToString(aInfo.mIpProto);
1044 
1045     if (aInfo.mIpProto == OT_IP6_PROTO_ICMP6)
1046     {
1047         switch (aInfo.mIcmp6Type)
1048         {
1049         case OT_ICMP6_TYPE_DST_UNREACH:
1050             str = "ICMP6(Unreach)";
1051             break;
1052         case OT_ICMP6_TYPE_PACKET_TO_BIG:
1053             str = "ICMP6(TooBig)";
1054             break;
1055         case OT_ICMP6_TYPE_ECHO_REQUEST:
1056             str = "ICMP6(EchoReqst)";
1057             break;
1058         case OT_ICMP6_TYPE_ECHO_REPLY:
1059             str = "ICMP6(EchoReply)";
1060             break;
1061         case OT_ICMP6_TYPE_ROUTER_SOLICIT:
1062             str = "ICMP6(RouterSol)";
1063             break;
1064         case OT_ICMP6_TYPE_ROUTER_ADVERT:
1065             str = "ICMP6(RouterAdv)";
1066             break;
1067         default:
1068             str = "ICMP6(Other)";
1069             break;
1070         }
1071     }
1072 
1073     return str;
1074 }
1075 
ProcessRxTxHistory(RxTx aRxTx,Arg aArgs[])1076 otError History::ProcessRxTxHistory(RxTx aRxTx, Arg aArgs[])
1077 {
1078     otError                            error;
1079     bool                               isList;
1080     uint16_t                           numEntries;
1081     otHistoryTrackerIterator           rxIterator;
1082     otHistoryTrackerIterator           txIterator;
1083     bool                               isRx   = false;
1084     const otHistoryTrackerMessageInfo *info   = nullptr;
1085     const otHistoryTrackerMessageInfo *rxInfo = nullptr;
1086     const otHistoryTrackerMessageInfo *txInfo = nullptr;
1087     uint32_t                           entryAge;
1088     uint32_t                           rxEntryAge;
1089     uint32_t                           txEntryAge;
1090 
1091     // | Age                  | Type             | Len   | Chksum | Sec | Prio | RSS  |Dir | Neighb | Radio |
1092     // +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
1093 
1094     static const char *const kTableTitles[] = {"Age",  "Type", "Len", "Chksum", "Sec",
1095                                                "Prio", "RSS",  "Dir", "Neighb", "Radio"};
1096 
1097     static const uint8_t kTableColumnWidths[] = {22, 18, 7, 8, 5, 6, 6, 4, 8, 7};
1098 
1099     SuccessOrExit(error = ParseArgs(aArgs, isList, numEntries));
1100 
1101     if (!isList)
1102     {
1103         OutputTableHeader(kTableTitles, kTableColumnWidths);
1104     }
1105 
1106     otHistoryTrackerInitIterator(&txIterator);
1107     otHistoryTrackerInitIterator(&rxIterator);
1108 
1109     for (uint16_t index = 0; (numEntries == 0) || (index < numEntries); index++)
1110     {
1111         switch (aRxTx)
1112         {
1113         case kRx:
1114             info = otHistoryTrackerIterateRxHistory(GetInstancePtr(), &rxIterator, &entryAge);
1115             isRx = true;
1116             break;
1117 
1118         case kTx:
1119             info = otHistoryTrackerIterateTxHistory(GetInstancePtr(), &txIterator, &entryAge);
1120             isRx = false;
1121             break;
1122 
1123         case kRxTx:
1124             // Iterate through both RX and TX lists and determine the entry
1125             // with earlier age.
1126 
1127             if (rxInfo == nullptr)
1128             {
1129                 rxInfo = otHistoryTrackerIterateRxHistory(GetInstancePtr(), &rxIterator, &rxEntryAge);
1130             }
1131 
1132             if (txInfo == nullptr)
1133             {
1134                 txInfo = otHistoryTrackerIterateTxHistory(GetInstancePtr(), &txIterator, &txEntryAge);
1135             }
1136 
1137             if ((rxInfo != nullptr) && ((txInfo == nullptr) || (rxEntryAge <= txEntryAge)))
1138             {
1139                 info     = rxInfo;
1140                 entryAge = rxEntryAge;
1141                 isRx     = true;
1142                 rxInfo   = nullptr;
1143             }
1144             else
1145             {
1146                 info     = txInfo;
1147                 entryAge = txEntryAge;
1148                 isRx     = false;
1149                 txInfo   = nullptr;
1150             }
1151 
1152             break;
1153         }
1154 
1155         VerifyOrExit(info != nullptr);
1156 
1157         if (isList)
1158         {
1159             OutputRxTxEntryListFormat(*info, entryAge, isRx);
1160         }
1161         else
1162         {
1163             if (index != 0)
1164             {
1165                 OutputTableSeparator(kTableColumnWidths);
1166             }
1167 
1168             OutputRxTxEntryTableFormat(*info, entryAge, isRx);
1169         }
1170     }
1171 
1172 exit:
1173     return error;
1174 }
1175 
OutputRxTxEntryListFormat(const otHistoryTrackerMessageInfo & aInfo,uint32_t aEntryAge,bool aIsRx)1176 void History::OutputRxTxEntryListFormat(const otHistoryTrackerMessageInfo &aInfo, uint32_t aEntryAge, bool aIsRx)
1177 {
1178     constexpr uint8_t kIndentSize = 4;
1179 
1180     char ageString[OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE];
1181 
1182     otHistoryTrackerEntryAgeToString(aEntryAge, ageString, sizeof(ageString));
1183 
1184     OutputLine("%s", ageString);
1185     OutputFormat(kIndentSize, "type:%s len:%u checksum:0x%04x sec:%s prio:%s ", MessageTypeToString(aInfo),
1186                  aInfo.mPayloadLength, aInfo.mChecksum, aInfo.mLinkSecurity ? "yes" : "no",
1187                  MessagePriorityToString(aInfo.mPriority));
1188     if (aIsRx)
1189     {
1190         OutputFormat("rss:%d", aInfo.mAveRxRss);
1191     }
1192     else
1193     {
1194         OutputFormat("tx-success:%s", aInfo.mTxSuccess ? "yes" : "no");
1195     }
1196 
1197     OutputLine(" %s:0x%04x radio:%s", aIsRx ? "from" : "to", aInfo.mNeighborRloc16, RadioTypeToString(aInfo));
1198 
1199     OutputFormat(kIndentSize, "src:");
1200     OutputSockAddrLine(aInfo.mSource);
1201 
1202     OutputFormat(kIndentSize, "dst:");
1203     OutputSockAddrLine(aInfo.mDestination);
1204 }
1205 
OutputRxTxEntryTableFormat(const otHistoryTrackerMessageInfo & aInfo,uint32_t aEntryAge,bool aIsRx)1206 void History::OutputRxTxEntryTableFormat(const otHistoryTrackerMessageInfo &aInfo, uint32_t aEntryAge, bool aIsRx)
1207 {
1208     char ageString[OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE];
1209     char addrString[OT_IP6_SOCK_ADDR_STRING_SIZE];
1210 
1211     otHistoryTrackerEntryAgeToString(aEntryAge, ageString, sizeof(ageString));
1212 
1213     OutputFormat("| %20s | %-16.16s | %5u | 0x%04x | %3s | %4s | ", "", MessageTypeToString(aInfo),
1214                  aInfo.mPayloadLength, aInfo.mChecksum, aInfo.mLinkSecurity ? "yes" : "no",
1215                  MessagePriorityToString(aInfo.mPriority));
1216 
1217     if (aIsRx)
1218     {
1219         OutputFormat("%4d | RX ", aInfo.mAveRxRss);
1220     }
1221     else
1222     {
1223         OutputFormat(" NA  |");
1224         OutputFormat(aInfo.mTxSuccess ? " TX " : "TX-F");
1225     }
1226 
1227     if (aInfo.mNeighborRloc16 == kShortAddrBroadcast)
1228     {
1229         OutputFormat("| bcast  ");
1230     }
1231     else if (aInfo.mNeighborRloc16 == kShortAddrInvalid)
1232     {
1233         OutputFormat("| unknwn ");
1234     }
1235     else
1236     {
1237         OutputFormat("| 0x%04x ", aInfo.mNeighborRloc16);
1238     }
1239 
1240     OutputLine("| %5.5s |", RadioTypeToString(aInfo));
1241 
1242     otIp6SockAddrToString(&aInfo.mSource, addrString, sizeof(addrString));
1243     OutputLine("| %20s | src: %-70s |", ageString, addrString);
1244 
1245     otIp6SockAddrToString(&aInfo.mDestination, addrString, sizeof(addrString));
1246     OutputLine("| %20s | dst: %-70s |", "", addrString);
1247 }
1248 
1249 /**
1250  * @cli history prefix
1251  * @code
1252  * history prefix
1253  * | Age                  | Event   | Prefix                                      | Flags     | Pref | RLOC16 |
1254  * +----------------------+---------+---------------------------------------------+-----------+------+--------+
1255  * |         00:00:10.663 | Added   | fd00:1111:2222:3333::/64                    | paro      | med  | 0x5400 |
1256  * |         00:01:02.054 | Removed | fd00:dead:beef:1::/64                       | paros     | high | 0x5400 |
1257  * |         00:01:21.136 | Added   | fd00:abba:cddd:0::/64                       | paos      | med  | 0x5400 |
1258  * |         00:01:45.144 | Added   | fd00:dead:beef:1::/64                       | paros     | high | 0x3c00 |
1259  * |         00:01:50.944 | Added   | fd00:dead:beef:1::/64                       | paros     | high | 0x5400 |
1260  * |         00:01:59.887 | Added   | fd00:dead:beef:1::/64                       | paros     | med  | 0x8800 |
1261  * Done
1262  * @endcode
1263  * @code
1264  * history prefix list
1265  * 00:04:12.487 -> event:Added prefix:fd00:1111:2222:3333::/64 flags:paro pref:med rloc16:0x5400
1266  * 00:05:03.878 -> event:Removed prefix:fd00:dead:beef:1::/64 flags:paros pref:high rloc16:0x5400
1267  * 00:05:22.960 -> event:Added prefix:fd00:abba:cddd:0::/64 flags:paos pref:med rloc16:0x5400
1268  * 00:05:46.968 -> event:Added prefix:fd00:dead:beef:1::/64 flags:paros pref:high rloc16:0x3c00
1269  * 00:05:52.768 -> event:Added prefix:fd00:dead:beef:1::/64 flags:paros pref:high rloc16:0x5400
1270  * 00:06:01.711 -> event:Added prefix:fd00:dead:beef:1::/64 flags:paros pref:med rloc16:0x8800
1271  * Done
1272  * @endcode
1273  * @cparam history prefix [@ca{list}] [@ca{num-entries}]
1274  * * Use the `list` option to display the output in list format. Otherwise,
1275  *   the output is shown in table format.
1276  * * Use the `num-entries` option to limit the output to the number of
1277  *   most-recent entries specified. If this option is not used, all stored
1278  *   entries are shown in the output.
1279  * @par
1280  * Displays the network data for the mesh prefix history in table or list format.
1281  * @par
1282  * Each table or list entry provides:
1283  * * Age: Time elapsed since the command was issued, and given in the format:
1284  *        `hours`:`minutes`:`seconds`:`milliseconds`
1285  * * Event: Possible values are `Added` or `Removed`.
1286  * * Prefix
1287  * * Flags/meaning:
1288  *     * `p`: Preferred flag
1289  *     * `a`: Stateless IPv6 address auto-configuration flag.
1290  *     * `d`: DHCPv6 IPv6 address configuration flag.
1291  *     * `c`: DHCPv6 other-configuration flag.
1292  *     * `r`: Default route flag.
1293  *     * `o`: On mesh flag.
1294  *     * `s`: Stable flag.
1295  *     * `n`: Nd Dns flag.
1296  *     * `D`: Domain prefix flag.
1297  * * Pref: Preference. Values can be either `high`, `med`, or `low`.
1298  * * RLOC16
1299  * @sa otHistoryTrackerIterateOnMeshPrefixHistory
1300  */
Process(Arg aArgs[])1301 template <> otError History::Process<Cmd("prefix")>(Arg aArgs[])
1302 {
1303     otError                                 error;
1304     bool                                    isList;
1305     uint16_t                                numEntries;
1306     otHistoryTrackerIterator                iterator;
1307     const otHistoryTrackerOnMeshPrefixInfo *info;
1308     uint32_t                                entryAge;
1309     char                                    ageString[OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE];
1310     char                                    prefixString[OT_IP6_PREFIX_STRING_SIZE];
1311     NetworkData::FlagsString                flagsString;
1312 
1313     static_assert(0 == OT_HISTORY_TRACKER_NET_DATA_ENTRY_ADDED, "NET_DATA_ENTRY_ADDED value is incorrect");
1314     static_assert(1 == OT_HISTORY_TRACKER_NET_DATA_ENTRY_REMOVED, "NET_DATA_ENTRY_REMOVED value is incorrect");
1315 
1316     SuccessOrExit(error = ParseArgs(aArgs, isList, numEntries));
1317 
1318     if (!isList)
1319     {
1320         // | Age                  | Event   | Prefix                                      | Flags     | Pref | RLOC16 |
1321         // +----------------------+---------+---------------------------------------------+-----------+------+--------+
1322 
1323         static const char *const kPrefixTitles[]       = {"Age", "Event", "Prefix", "Flags", "Pref", "RLOC16"};
1324         static const uint8_t     kPrefixColumnWidths[] = {22, 9, 45, 11, 6, 8};
1325 
1326         OutputTableHeader(kPrefixTitles, kPrefixColumnWidths);
1327     }
1328 
1329     otHistoryTrackerInitIterator(&iterator);
1330 
1331     for (uint16_t index = 0; (numEntries == 0) || (index < numEntries); index++)
1332     {
1333         info = otHistoryTrackerIterateOnMeshPrefixHistory(GetInstancePtr(), &iterator, &entryAge);
1334         VerifyOrExit(info != nullptr);
1335 
1336         otHistoryTrackerEntryAgeToString(entryAge, ageString, sizeof(ageString));
1337 
1338         otIp6PrefixToString(&info->mPrefix.mPrefix, prefixString, sizeof(prefixString));
1339         NetworkData::PrefixFlagsToString(info->mPrefix, flagsString);
1340 
1341         OutputLine(isList ? "%s -> event:%s prefix:%s flags:%s pref:%s rloc16:0x%04x"
1342                           : "| %20s | %-7s | %-43s | %-9s | %-4s | 0x%04x |",
1343                    ageString, Stringify(info->mEvent, kSimpleEventStrings), prefixString, flagsString,
1344                    PreferenceToString(info->mPrefix.mPreference), info->mPrefix.mRloc16);
1345     }
1346 
1347 exit:
1348     return error;
1349 }
1350 
1351 /**
1352  * @cli history route
1353  * @code
1354  * history route
1355  * | Age                  | Event   | Route                                       | Flags     | Pref | RLOC16 |
1356  * +----------------------+---------+---------------------------------------------+-----------+------+--------+
1357  * |         00:00:05.456 | Removed | fd00:1111:0::/48                            | s         | med  | 0x3c00 |
1358  * |         00:00:29.310 | Added   | fd00:1111:0::/48                            | s         | med  | 0x3c00 |
1359  * |         00:00:42.822 | Added   | fd00:1111:0::/48                            | s         | med  | 0x5400 |
1360  * |         00:01:27.688 | Added   | fd00:aaaa:bbbb:cccc::/64                    | s         | med  | 0x8800 |
1361  * Done
1362  * @endcode
1363  * @code
1364  * history route list 2
1365  * 00:00:48.704 -> event:Removed route:fd00:1111:0::/48 flags:s pref:med rloc16:0x3c00
1366  * 00:01:12.558 -> event:Added route:fd00:1111:0::/48 flags:s pref:med rloc16:0x3c00
1367  * Done
1368  * @endcode
1369  * @cparam history route [@ca{list}] [@ca{num-entries}]
1370  * * Use the `list` option to display the output in list format. Otherwise,
1371  *   the output is shown in table format.
1372  * * Use the `num-entries` option to limit the output to the number of
1373  *   most-recent entries specified. If this option is not used, all stored
1374  *   entries are shown in the output.
1375  * @par
1376  * Displays the network data external-route history in table or list format.
1377  * @par
1378  * Each table or list entry provides:
1379  * * Age: Time elapsed since the command was issued, and given in the format:
1380  *        `hours`:`minutes`:`seconds`:`milliseconds`
1381  * * Event: Possible values are `Added` or `Removed`.
1382  * * Route
1383  * * Flags/meaning:
1384  *     * `s`: Stable flag.
1385  *     * `n`: NAT64 flag.
1386  * * Pref: Preference. Values can be either `high`, `med`, or `low`.
1387  * * RLOC16
1388  * @sa otHistoryTrackerIterateExternalRouteHistory
1389  */
Process(Arg aArgs[])1390 template <> otError History::Process<Cmd("route")>(Arg aArgs[])
1391 {
1392     otError                                  error;
1393     bool                                     isList;
1394     uint16_t                                 numEntries;
1395     otHistoryTrackerIterator                 iterator;
1396     const otHistoryTrackerExternalRouteInfo *info;
1397     uint32_t                                 entryAge;
1398     char                                     ageString[OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE];
1399     char                                     prefixString[OT_IP6_PREFIX_STRING_SIZE];
1400     NetworkData::FlagsString                 flagsString;
1401 
1402     static_assert(0 == OT_HISTORY_TRACKER_NET_DATA_ENTRY_ADDED, "NET_DATA_ENTRY_ADDED value is incorrect");
1403     static_assert(1 == OT_HISTORY_TRACKER_NET_DATA_ENTRY_REMOVED, "NET_DATA_ENTRY_REMOVED value is incorrect");
1404 
1405     SuccessOrExit(error = ParseArgs(aArgs, isList, numEntries));
1406 
1407     if (!isList)
1408     {
1409         // | Age                  | Event   | Route                                       | Flags     | Pref | RLOC16 |
1410         // +----------------------+---------+---------------------------------------------+-----------+------+--------+
1411 
1412         static const char *const kRouteTitles[]       = {"Age", "Event", "Route", "Flags", "Pref", "RLOC16"};
1413         static const uint8_t     kRouteColumnWidths[] = {22, 9, 45, 11, 6, 8};
1414 
1415         OutputTableHeader(kRouteTitles, kRouteColumnWidths);
1416     }
1417 
1418     otHistoryTrackerInitIterator(&iterator);
1419 
1420     for (uint16_t index = 0; (numEntries == 0) || (index < numEntries); index++)
1421     {
1422         info = otHistoryTrackerIterateExternalRouteHistory(GetInstancePtr(), &iterator, &entryAge);
1423         VerifyOrExit(info != nullptr);
1424 
1425         otHistoryTrackerEntryAgeToString(entryAge, ageString, sizeof(ageString));
1426 
1427         otIp6PrefixToString(&info->mRoute.mPrefix, prefixString, sizeof(prefixString));
1428         NetworkData::RouteFlagsToString(info->mRoute, flagsString);
1429 
1430         OutputLine(isList ? "%s -> event:%s route:%s flags:%s pref:%s rloc16:0x%04x"
1431                           : "| %20s | %-7s | %-43s | %-9s | %-4s | 0x%04x |",
1432                    ageString, Stringify(info->mEvent, kSimpleEventStrings), prefixString, flagsString,
1433                    PreferenceToString(info->mRoute.mPreference), info->mRoute.mRloc16);
1434     }
1435 
1436 exit:
1437     return error;
1438 }
1439 
Process(Arg aArgs[])1440 otError History::Process(Arg aArgs[])
1441 {
1442 #define CmdEntry(aCommandString)                               \
1443     {                                                          \
1444         aCommandString, &History::Process<Cmd(aCommandString)> \
1445     }
1446 
1447     static constexpr Command kCommands[] = {
1448         CmdEntry("ipaddr"), CmdEntry("ipmaddr"), CmdEntry("neighbor"), CmdEntry("netinfo"), CmdEntry("prefix"),
1449         CmdEntry("route"),  CmdEntry("router"),  CmdEntry("rx"),       CmdEntry("rxtx"),    CmdEntry("tx"),
1450     };
1451 
1452 #undef CmdEntry
1453 
1454     static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted");
1455 
1456     otError        error = OT_ERROR_INVALID_COMMAND;
1457     const Command *command;
1458 
1459     if (aArgs[0].IsEmpty() || (aArgs[0] == "help"))
1460     {
1461         OutputCommandTable(kCommands);
1462         ExitNow(error = aArgs[0].IsEmpty() ? error : OT_ERROR_NONE);
1463     }
1464 
1465     command = BinarySearch::Find(aArgs[0].GetCString(), kCommands);
1466     VerifyOrExit(command != nullptr);
1467 
1468     error = (this->*command->mHandler)(aArgs + 1);
1469 
1470 exit:
1471     return error;
1472 }
1473 
1474 } // namespace Cli
1475 } // namespace ot
1476 
1477 #endif // OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
1478