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