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 constexpr History::Command History::sCommands[];
46
ProcessHelp(Arg aArgs[])47 otError History::ProcessHelp(Arg aArgs[])
48 {
49 OT_UNUSED_VARIABLE(aArgs);
50
51 for (const Command &command : sCommands)
52 {
53 mInterpreter.OutputLine(command.mName);
54 }
55
56 return OT_ERROR_NONE;
57 }
58
ParseArgs(Arg aArgs[],bool & aIsList,uint16_t & aNumEntries) const59 otError History::ParseArgs(Arg aArgs[], bool &aIsList, uint16_t &aNumEntries) const
60 {
61 if (*aArgs == "list")
62 {
63 aArgs++;
64 aIsList = true;
65 }
66 else
67 {
68 aIsList = false;
69 }
70
71 if (aArgs->ParseAsUint16(aNumEntries) == OT_ERROR_NONE)
72 {
73 aArgs++;
74 }
75 else
76 {
77 aNumEntries = 0;
78 }
79
80 return aArgs[0].IsEmpty() ? OT_ERROR_NONE : OT_ERROR_INVALID_ARGS;
81 }
82
ProcessNeighbor(Arg aArgs[])83 otError History::ProcessNeighbor(Arg aArgs[])
84 {
85 static const char *const kEventString[] = {
86 /* (0) OT_HISTORY_TRACKER_NEIGHBOR_EVENT_ADDED -> */ "Added",
87 /* (1) OT_HISTORY_TRACKER_NEIGHBOR_EVENT_REMOVED -> */ "Removed",
88 /* (2) OT_HISTORY_TRACKER_NEIGHBOR_EVENT_CHANGED -> */ "Changed",
89 /* (3) OT_HISTORY_TRACKER_NEIGHBOR_EVENT_RESTORING -> */ "Restoring",
90 };
91
92 otError error;
93 bool isList;
94 uint16_t numEntries;
95 otHistoryTrackerIterator iterator;
96 const otHistoryTrackerNeighborInfo *info;
97 uint32_t entryAge;
98 char ageString[OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE];
99 otLinkModeConfig mode;
100 char linkModeString[Interpreter::kLinkModeStringSize];
101
102 static_assert(0 == OT_HISTORY_TRACKER_NEIGHBOR_EVENT_ADDED, "NEIGHBOR_EVENT_ADDED value is incorrect");
103 static_assert(1 == OT_HISTORY_TRACKER_NEIGHBOR_EVENT_REMOVED, "NEIGHBOR_EVENT_REMOVED value is incorrect");
104 static_assert(2 == OT_HISTORY_TRACKER_NEIGHBOR_EVENT_CHANGED, "NEIGHBOR_EVENT_CHANGED value is incorrect");
105 static_assert(3 == OT_HISTORY_TRACKER_NEIGHBOR_EVENT_RESTORING, "NEIGHBOR_EVENT_RESTORING value is incorrect");
106
107 SuccessOrExit(error = ParseArgs(aArgs, isList, numEntries));
108
109 if (!isList)
110 {
111 // | Age | Type | Event | Extended Address | RLOC16 | Mode | Ave RSS |
112 // +----------------------+--------+-----------+------------------+--------+------+---------+
113
114 static const char *const kNeighborInfoTitles[] = {
115 "Age", "Type", "Event", "Extended Address", "RLOC16", "Mode", "Ave RSS",
116 };
117
118 static const uint8_t kNeighborInfoColumnWidths[] = {22, 8, 11, 18, 8, 6, 9};
119
120 mInterpreter.OutputTableHeader(kNeighborInfoTitles, kNeighborInfoColumnWidths);
121 }
122
123 otHistoryTrackerInitIterator(&iterator);
124
125 for (uint16_t index = 0; (numEntries == 0) || (index < numEntries); index++)
126 {
127 info = otHistoryTrackerIterateNeighborHistory(mInterpreter.mInstance, &iterator, &entryAge);
128 VerifyOrExit(info != nullptr);
129
130 otHistoryTrackerEntryAgeToString(entryAge, ageString, sizeof(ageString));
131
132 mode.mRxOnWhenIdle = info->mRxOnWhenIdle;
133 mode.mDeviceType = info->mFullThreadDevice;
134 mode.mNetworkData = info->mFullNetworkData;
135 Interpreter::LinkModeToString(mode, linkModeString);
136
137 mInterpreter.OutputFormat(isList ? "%s -> type:%s event:%s extaddr:" : "| %20s | %-6s | %-9s | ", ageString,
138 info->mIsChild ? "Child" : "Router", kEventString[info->mEvent]);
139 mInterpreter.OutputExtAddress(info->mExtAddress);
140 mInterpreter.OutputLine(isList ? " rloc16:0x%04x mode:%s rss:%d" : " | 0x%04x | %-4s | %7d |", info->mRloc16,
141 linkModeString, info->mAverageRssi);
142 }
143
144 exit:
145 return error;
146 }
147
ProcessNetInfo(Arg aArgs[])148 otError History::ProcessNetInfo(Arg aArgs[])
149 {
150 otError error;
151 bool isList;
152 uint16_t numEntries;
153 otHistoryTrackerIterator iterator;
154 const otHistoryTrackerNetworkInfo *info;
155 uint32_t entryAge;
156 char ageString[OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE];
157 char linkModeString[Interpreter::kLinkModeStringSize];
158
159 SuccessOrExit(error = ParseArgs(aArgs, isList, numEntries));
160
161 if (!isList)
162 {
163 // | Age | Role | Mode | RLOC16 | Partition ID |
164 // +----------------------+----------+------+--------+--------------+
165
166 static const char *const kNetInfoTitles[] = {"Age", "Role", "Mode", "RLOC16", "Partition ID"};
167 static const uint8_t kNetInfoColumnWidths[] = {22, 10, 6, 8, 14};
168
169 mInterpreter.OutputTableHeader(kNetInfoTitles, kNetInfoColumnWidths);
170 }
171
172 otHistoryTrackerInitIterator(&iterator);
173
174 for (uint16_t index = 0; (numEntries == 0) || (index < numEntries); index++)
175 {
176 info = otHistoryTrackerIterateNetInfoHistory(mInterpreter.mInstance, &iterator, &entryAge);
177 VerifyOrExit(info != nullptr);
178
179 otHistoryTrackerEntryAgeToString(entryAge, ageString, sizeof(ageString));
180
181 mInterpreter.OutputLine(
182 isList ? "%s -> role:%s mode:%s rloc16:0x%04x partition-id:%u" : "| %20s | %-8s | %-4s | 0x%04x | %12u |",
183 ageString, otThreadDeviceRoleToString(info->mRole),
184 Interpreter::LinkModeToString(info->mMode, linkModeString), info->mRloc16, info->mPartitionId);
185 }
186
187 exit:
188 return error;
189 }
190
ProcessRx(Arg aArgs[])191 otError History::ProcessRx(Arg aArgs[])
192 {
193 return ProcessRxTxHistory(kRx, aArgs);
194 }
195
ProcessRxTx(Arg aArgs[])196 otError History::ProcessRxTx(Arg aArgs[])
197 {
198 return ProcessRxTxHistory(kRxTx, aArgs);
199 }
200
ProcessTx(Arg aArgs[])201 otError History::ProcessTx(Arg aArgs[])
202 {
203 return ProcessRxTxHistory(kTx, aArgs);
204 }
205
MessagePriorityToString(uint8_t aPriority)206 const char *History::MessagePriorityToString(uint8_t aPriority)
207 {
208 const char *str = "unkn";
209
210 switch (aPriority)
211 {
212 case OT_HISTORY_TRACKER_MSG_PRIORITY_LOW:
213 str = "low";
214 break;
215
216 case OT_HISTORY_TRACKER_MSG_PRIORITY_NORMAL:
217 str = "norm";
218 break;
219
220 case OT_HISTORY_TRACKER_MSG_PRIORITY_HIGH:
221 str = "high";
222 break;
223
224 case OT_HISTORY_TRACKER_MSG_PRIORITY_NET:
225 str = "net";
226 break;
227
228 default:
229 break;
230 }
231
232 return str;
233 }
234
RadioTypeToString(const otHistoryTrackerMessageInfo & aInfo)235 const char *History::RadioTypeToString(const otHistoryTrackerMessageInfo &aInfo)
236 {
237 const char *str = "none";
238
239 if (aInfo.mRadioTrelUdp6 && aInfo.mRadioIeee802154)
240 {
241 str = "all";
242 }
243 else if (aInfo.mRadioIeee802154)
244 {
245 str = "15.4";
246 }
247 else if (aInfo.mRadioTrelUdp6)
248 {
249 str = "trel";
250 }
251
252 return str;
253 }
254
MessageTypeToString(const otHistoryTrackerMessageInfo & aInfo)255 const char *History::MessageTypeToString(const otHistoryTrackerMessageInfo &aInfo)
256 {
257 const char *str = otIp6ProtoToString(aInfo.mIpProto);
258
259 if (aInfo.mIpProto == OT_IP6_PROTO_ICMP6)
260 {
261 switch (aInfo.mIcmp6Type)
262 {
263 case OT_ICMP6_TYPE_DST_UNREACH:
264 str = "ICMP6(Unreach)";
265 break;
266 case OT_ICMP6_TYPE_PACKET_TO_BIG:
267 str = "ICMP6(TooBig)";
268 break;
269 case OT_ICMP6_TYPE_ECHO_REQUEST:
270 str = "ICMP6(EchoReqst)";
271 break;
272 case OT_ICMP6_TYPE_ECHO_REPLY:
273 str = "ICMP6(EchoReply)";
274 break;
275 case OT_ICMP6_TYPE_ROUTER_SOLICIT:
276 str = "ICMP6(RouterSol)";
277 break;
278 case OT_ICMP6_TYPE_ROUTER_ADVERT:
279 str = "ICMP6(RouterAdv)";
280 break;
281 default:
282 str = "ICMP6(Other)";
283 break;
284 }
285 }
286
287 return str;
288 }
289
ProcessRxTxHistory(RxTx aRxTx,Arg aArgs[])290 otError History::ProcessRxTxHistory(RxTx aRxTx, Arg aArgs[])
291 {
292 otError error;
293 bool isList;
294 uint16_t numEntries;
295 otHistoryTrackerIterator rxIterator;
296 otHistoryTrackerIterator txIterator;
297 bool isRx = false;
298 const otHistoryTrackerMessageInfo *info = nullptr;
299 const otHistoryTrackerMessageInfo *rxInfo = nullptr;
300 const otHistoryTrackerMessageInfo *txInfo = nullptr;
301 uint32_t entryAge;
302 uint32_t rxEntryAge;
303 uint32_t txEntryAge;
304
305 // | Age | Type | Len | Chksum | Sec | Prio | RSS |Dir | Neighb | Radio |
306 // +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
307
308 static const char *const kTableTitles[] = {"Age", "Type", "Len", "Chksum", "Sec",
309 "Prio", "RSS", "Dir", "Neighb", "Radio"};
310
311 static const uint8_t kTableColumnWidths[] = {22, 18, 7, 8, 5, 6, 6, 4, 8, 7};
312
313 SuccessOrExit(error = ParseArgs(aArgs, isList, numEntries));
314
315 if (!isList)
316 {
317 mInterpreter.OutputTableHeader(kTableTitles, kTableColumnWidths);
318 }
319
320 otHistoryTrackerInitIterator(&txIterator);
321 otHistoryTrackerInitIterator(&rxIterator);
322
323 for (uint16_t index = 0; (numEntries == 0) || (index < numEntries); index++)
324 {
325 switch (aRxTx)
326 {
327 case kRx:
328 info = otHistoryTrackerIterateRxHistory(mInterpreter.mInstance, &rxIterator, &entryAge);
329 isRx = true;
330 break;
331
332 case kTx:
333 info = otHistoryTrackerIterateTxHistory(mInterpreter.mInstance, &txIterator, &entryAge);
334 isRx = false;
335 break;
336
337 case kRxTx:
338 // Iterate through both RX and TX lists and determine the entry
339 // with earlier age.
340
341 if (rxInfo == nullptr)
342 {
343 rxInfo = otHistoryTrackerIterateRxHistory(mInterpreter.mInstance, &rxIterator, &rxEntryAge);
344 }
345
346 if (txInfo == nullptr)
347 {
348 txInfo = otHistoryTrackerIterateTxHistory(mInterpreter.mInstance, &txIterator, &txEntryAge);
349 }
350
351 if ((rxInfo != nullptr) && ((txInfo == nullptr) || (rxEntryAge <= txEntryAge)))
352 {
353 info = rxInfo;
354 entryAge = rxEntryAge;
355 isRx = true;
356 rxInfo = nullptr;
357 }
358 else
359 {
360 info = txInfo;
361 entryAge = txEntryAge;
362 isRx = false;
363 txInfo = nullptr;
364 }
365
366 break;
367 }
368
369 VerifyOrExit(info != nullptr);
370
371 if (isList)
372 {
373 OutputRxTxEntryListFormat(*info, entryAge, isRx);
374 }
375 else
376 {
377 if (index != 0)
378 {
379 mInterpreter.OutputTableSeperator(kTableColumnWidths);
380 }
381
382 OutputRxTxEntryTableFormat(*info, entryAge, isRx);
383 }
384 }
385
386 exit:
387 return error;
388 }
389
OutputRxTxEntryListFormat(const otHistoryTrackerMessageInfo & aInfo,uint32_t aEntryAge,bool aIsRx)390 void History::OutputRxTxEntryListFormat(const otHistoryTrackerMessageInfo &aInfo, uint32_t aEntryAge, bool aIsRx)
391 {
392 constexpr uint8_t kIndentSize = 4;
393
394 char ageString[OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE];
395 char addrString[OT_IP6_SOCK_ADDR_STRING_SIZE];
396
397 otHistoryTrackerEntryAgeToString(aEntryAge, ageString, sizeof(ageString));
398
399 mInterpreter.OutputLine("%s", ageString);
400 mInterpreter.OutputFormat(kIndentSize, "type:%s len:%u cheksum:0x%04x sec:%s prio:%s ", MessageTypeToString(aInfo),
401 aInfo.mPayloadLength, aInfo.mChecksum, aInfo.mLinkSecurity ? "yes" : "no",
402 MessagePriorityToString(aInfo.mPriority));
403 if (aIsRx)
404 {
405 mInterpreter.OutputFormat("rss:%d", aInfo.mAveRxRss);
406 }
407 else
408 {
409 mInterpreter.OutputFormat("tx-success:%s", aInfo.mTxSuccess ? "yes" : "no");
410 }
411
412 mInterpreter.OutputLine(" %s:0x%04x radio:%s", aIsRx ? "from" : "to", aInfo.mNeighborRloc16,
413 RadioTypeToString(aInfo));
414
415 otIp6SockAddrToString(&aInfo.mSource, addrString, sizeof(addrString));
416 mInterpreter.OutputLine(kIndentSize, "src:%s", addrString);
417
418 otIp6SockAddrToString(&aInfo.mDestination, addrString, sizeof(addrString));
419 mInterpreter.OutputLine(kIndentSize, "dst:%s", addrString);
420 }
421
OutputRxTxEntryTableFormat(const otHistoryTrackerMessageInfo & aInfo,uint32_t aEntryAge,bool aIsRx)422 void History::OutputRxTxEntryTableFormat(const otHistoryTrackerMessageInfo &aInfo, uint32_t aEntryAge, bool aIsRx)
423 {
424 char ageString[OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE];
425 char addrString[OT_IP6_SOCK_ADDR_STRING_SIZE];
426
427 otHistoryTrackerEntryAgeToString(aEntryAge, ageString, sizeof(ageString));
428
429 mInterpreter.OutputFormat("| %20s | %-16.16s | %5u | 0x%04x | %3s | %4s | ", "", MessageTypeToString(aInfo),
430 aInfo.mPayloadLength, aInfo.mChecksum, aInfo.mLinkSecurity ? "yes" : "no",
431 MessagePriorityToString(aInfo.mPriority));
432
433 if (aIsRx)
434 {
435 mInterpreter.OutputFormat("%4d | RX ", aInfo.mAveRxRss);
436 }
437 else
438 {
439 mInterpreter.OutputFormat(" NA |");
440 mInterpreter.OutputFormat(aInfo.mTxSuccess ? " TX " : "TX-F");
441 }
442
443 if (aInfo.mNeighborRloc16 == kShortAddrBroadcast)
444 {
445 mInterpreter.OutputFormat("| bcast ");
446 }
447 else if (aInfo.mNeighborRloc16 == kShortAddrInvalid)
448 {
449 mInterpreter.OutputFormat("| unknwn ");
450 }
451 else
452 {
453 mInterpreter.OutputFormat("| 0x%04x ", aInfo.mNeighborRloc16);
454 }
455
456 mInterpreter.OutputLine("| %5.5s |", RadioTypeToString(aInfo));
457
458 otIp6SockAddrToString(&aInfo.mSource, addrString, sizeof(addrString));
459 mInterpreter.OutputLine("| %20s | src: %-70s |", ageString, addrString);
460
461 otIp6SockAddrToString(&aInfo.mDestination, addrString, sizeof(addrString));
462 mInterpreter.OutputLine("| %20s | dst: %-70s |", "", addrString);
463 }
464
Process(Arg aArgs[])465 otError History::Process(Arg aArgs[])
466 {
467 otError error = OT_ERROR_INVALID_COMMAND;
468 const Command *command;
469
470 if (aArgs[0].IsEmpty())
471 {
472 IgnoreError(ProcessHelp(aArgs));
473 ExitNow();
474 }
475
476 command = Utils::LookupTable::Find(aArgs[0].GetCString(), sCommands);
477 VerifyOrExit(command != nullptr);
478
479 error = (this->*command->mHandler)(aArgs + 1);
480
481 exit:
482 return error;
483 }
484
485 } // namespace Cli
486 } // namespace ot
487
488 #endif // OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
489