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