1 /*
2  *  Copyright (c) 2016, 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 the diagnostics module.
32  */
33 
34 #include "factory_diags.hpp"
35 
36 #if OPENTHREAD_CONFIG_DIAG_ENABLE
37 
38 #include <stdio.h>
39 #include <stdlib.h>
40 
41 #include <openthread/platform/alarm-milli.h>
42 #include <openthread/platform/diag.h>
43 
44 #include "common/code_utils.hpp"
45 #include "common/instance.hpp"
46 #include "common/locator_getters.hpp"
47 #include "radio/radio.hpp"
48 #include "utils/parse_cmdline.hpp"
49 
50 OT_TOOL_WEAK
otPlatDiagProcess(otInstance * aInstance,uint8_t aArgsLength,char * aArgs[],char * aOutput,size_t aOutputMaxLen)51 otError otPlatDiagProcess(otInstance *aInstance,
52                           uint8_t     aArgsLength,
53                           char *      aArgs[],
54                           char *      aOutput,
55                           size_t      aOutputMaxLen)
56 {
57     OT_UNUSED_VARIABLE(aArgsLength);
58     OT_UNUSED_VARIABLE(aArgs);
59     OT_UNUSED_VARIABLE(aInstance);
60     OT_UNUSED_VARIABLE(aOutput);
61     OT_UNUSED_VARIABLE(aOutputMaxLen);
62 
63     return ot::kErrorInvalidCommand;
64 }
65 
66 namespace ot {
67 namespace FactoryDiags {
68 
69 #if OPENTHREAD_RADIO
70 
71 const struct Diags::Command Diags::sCommands[] = {
72     {"channel", &Diags::ProcessChannel},
73     {"power", &Diags::ProcessPower},
74     {"start", &Diags::ProcessStart},
75     {"stop", &Diags::ProcessStop},
76 };
77 
Diags(Instance & aInstance)78 Diags::Diags(Instance &aInstance)
79     : InstanceLocator(aInstance)
80 {
81 }
82 
ProcessChannel(uint8_t aArgsLength,char * aArgs[],char * aOutput,size_t aOutputMaxLen)83 Error Diags::ProcessChannel(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
84 {
85     Error error = kErrorNone;
86     long  value;
87 
88     VerifyOrExit(aArgsLength == 1, error = kErrorInvalidArgs);
89 
90     SuccessOrExit(error = ParseLong(aArgs[0], value));
91     VerifyOrExit(value >= Radio::kChannelMin && value <= Radio::kChannelMax, error = kErrorInvalidArgs);
92 
93     otPlatDiagChannelSet(static_cast<uint8_t>(value));
94 
95 exit:
96     AppendErrorResult(error, aOutput, aOutputMaxLen);
97     return error;
98 }
99 
ProcessPower(uint8_t aArgsLength,char * aArgs[],char * aOutput,size_t aOutputMaxLen)100 Error Diags::ProcessPower(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
101 {
102     Error error = kErrorNone;
103     long  value;
104 
105     VerifyOrExit(aArgsLength == 1, error = kErrorInvalidArgs);
106 
107     SuccessOrExit(error = ParseLong(aArgs[0], value));
108 
109     otPlatDiagTxPowerSet(static_cast<int8_t>(value));
110 
111 exit:
112     AppendErrorResult(error, aOutput, aOutputMaxLen);
113     return error;
114 }
115 
ProcessStart(uint8_t aArgsLength,char * aArgs[],char * aOutput,size_t aOutputMaxLen)116 Error Diags::ProcessStart(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
117 {
118     OT_UNUSED_VARIABLE(aArgsLength);
119     OT_UNUSED_VARIABLE(aArgs);
120     OT_UNUSED_VARIABLE(aOutput);
121     OT_UNUSED_VARIABLE(aOutputMaxLen);
122 
123     otPlatDiagModeSet(true);
124 
125     return kErrorNone;
126 }
127 
ProcessStop(uint8_t aArgsLength,char * aArgs[],char * aOutput,size_t aOutputMaxLen)128 Error Diags::ProcessStop(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
129 {
130     OT_UNUSED_VARIABLE(aArgsLength);
131     OT_UNUSED_VARIABLE(aArgs);
132     OT_UNUSED_VARIABLE(aOutput);
133     OT_UNUSED_VARIABLE(aOutputMaxLen);
134 
135     otPlatDiagModeSet(false);
136 
137     return kErrorNone;
138 }
139 
otPlatDiagAlarmFired(otInstance * aInstance)140 extern "C" void otPlatDiagAlarmFired(otInstance *aInstance)
141 {
142     otPlatDiagAlarmCallback(aInstance);
143 }
144 
145 #else // OPENTHREAD_RADIO
146 
147 const struct Diags::Command Diags::sCommands[] = {
148     {"channel", &Diags::ProcessChannel}, {"power", &Diags::ProcessPower}, {"radio", &Diags::ProcessRadio},
149     {"repeat", &Diags::ProcessRepeat},   {"send", &Diags::ProcessSend},   {"start", &Diags::ProcessStart},
150     {"stats", &Diags::ProcessStats},     {"stop", &Diags::ProcessStop},
151 };
152 
153 Diags::Diags(Instance &aInstance)
154     : InstanceLocator(aInstance)
155     , mTxPacket(&Get<Radio>().GetTransmitBuffer())
156     , mTxPeriod(0)
157     , mTxPackets(0)
158     , mChannel(20)
159     , mTxPower(0)
160     , mTxLen(0)
161     , mRepeatActive(false)
162 {
163     mStats.Clear();
164 }
165 
166 Error Diags::ProcessChannel(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
167 {
168     Error error = kErrorNone;
169 
170     VerifyOrExit(otPlatDiagModeGet(), error = kErrorInvalidState);
171 
172     if (aArgsLength == 0)
173     {
174         snprintf(aOutput, aOutputMaxLen, "channel: %d\r\n", mChannel);
175     }
176     else
177     {
178         long value;
179 
180         SuccessOrExit(error = ParseLong(aArgs[0], value));
181         VerifyOrExit(value >= Radio::kChannelMin && value <= Radio::kChannelMax, error = kErrorInvalidArgs);
182 
183         mChannel = static_cast<uint8_t>(value);
184         IgnoreError(Get<Radio>().Receive(mChannel));
185         otPlatDiagChannelSet(mChannel);
186 
187         snprintf(aOutput, aOutputMaxLen, "set channel to %d\r\nstatus 0x%02x\r\n", mChannel, error);
188     }
189 
190 exit:
191     AppendErrorResult(error, aOutput, aOutputMaxLen);
192     return error;
193 }
194 
195 Error Diags::ProcessPower(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
196 {
197     Error error = kErrorNone;
198 
199     VerifyOrExit(otPlatDiagModeGet(), error = kErrorInvalidState);
200 
201     if (aArgsLength == 0)
202     {
203         snprintf(aOutput, aOutputMaxLen, "tx power: %d dBm\r\n", mTxPower);
204     }
205     else
206     {
207         long value;
208 
209         SuccessOrExit(error = ParseLong(aArgs[0], value));
210 
211         mTxPower = static_cast<int8_t>(value);
212         SuccessOrExit(error = Get<Radio>().SetTransmitPower(mTxPower));
213         otPlatDiagTxPowerSet(mTxPower);
214 
215         snprintf(aOutput, aOutputMaxLen, "set tx power to %d dBm\r\nstatus 0x%02x\r\n", mTxPower, error);
216     }
217 
218 exit:
219     AppendErrorResult(error, aOutput, aOutputMaxLen);
220     return error;
221 }
222 
223 Error Diags::ProcessRepeat(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
224 {
225     Error error = kErrorNone;
226 
227     VerifyOrExit(otPlatDiagModeGet(), error = kErrorInvalidState);
228     VerifyOrExit(aArgsLength > 0, error = kErrorInvalidArgs);
229 
230     if (strcmp(aArgs[0], "stop") == 0)
231     {
232         otPlatAlarmMilliStop(&GetInstance());
233         mRepeatActive = false;
234         snprintf(aOutput, aOutputMaxLen, "repeated packet transmission is stopped\r\nstatus 0x%02x\r\n", error);
235     }
236     else
237     {
238         long value;
239 
240         VerifyOrExit(aArgsLength == 2, error = kErrorInvalidArgs);
241 
242         SuccessOrExit(error = ParseLong(aArgs[0], value));
243         mTxPeriod = static_cast<uint32_t>(value);
244 
245         SuccessOrExit(error = ParseLong(aArgs[1], value));
246         VerifyOrExit(value <= OT_RADIO_FRAME_MAX_SIZE, error = kErrorInvalidArgs);
247         VerifyOrExit(value >= OT_RADIO_FRAME_MIN_SIZE, error = kErrorInvalidArgs);
248         mTxLen = static_cast<uint8_t>(value);
249 
250         mRepeatActive = true;
251         uint32_t now  = otPlatAlarmMilliGetNow();
252         otPlatAlarmMilliStartAt(&GetInstance(), now, mTxPeriod);
253         snprintf(aOutput, aOutputMaxLen, "sending packets of length %#x at the delay of %#x ms\r\nstatus 0x%02x\r\n",
254                  static_cast<int>(mTxLen), static_cast<int>(mTxPeriod), error);
255     }
256 
257 exit:
258     AppendErrorResult(error, aOutput, aOutputMaxLen);
259     return error;
260 }
261 
262 Error Diags::ProcessSend(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
263 {
264     Error error = kErrorNone;
265     long  value;
266 
267     VerifyOrExit(otPlatDiagModeGet(), error = kErrorInvalidState);
268     VerifyOrExit(aArgsLength == 2, error = kErrorInvalidArgs);
269 
270     SuccessOrExit(error = ParseLong(aArgs[0], value));
271     mTxPackets = static_cast<uint32_t>(value);
272 
273     SuccessOrExit(error = ParseLong(aArgs[1], value));
274     VerifyOrExit(value <= OT_RADIO_FRAME_MAX_SIZE, error = kErrorInvalidArgs);
275     VerifyOrExit(value >= OT_RADIO_FRAME_MIN_SIZE, error = kErrorInvalidArgs);
276     mTxLen = static_cast<uint8_t>(value);
277 
278     snprintf(aOutput, aOutputMaxLen, "sending %#x packet(s), length %#x\r\nstatus 0x%02x\r\n",
279              static_cast<int>(mTxPackets), static_cast<int>(mTxLen), error);
280     TransmitPacket();
281 
282 exit:
283     AppendErrorResult(error, aOutput, aOutputMaxLen);
284     return error;
285 }
286 
287 Error Diags::ProcessStart(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
288 {
289     OT_UNUSED_VARIABLE(aArgsLength);
290     OT_UNUSED_VARIABLE(aArgs);
291 
292     Error error = kErrorNone;
293 
294     VerifyOrExit(!Get<ThreadNetif>().IsUp(), error = kErrorInvalidState);
295 
296     otPlatDiagChannelSet(mChannel);
297     otPlatDiagTxPowerSet(mTxPower);
298 
299     IgnoreError(Get<Radio>().Enable());
300     Get<Radio>().SetPromiscuous(true);
301     otPlatAlarmMilliStop(&GetInstance());
302     SuccessOrExit(error = Get<Radio>().Receive(mChannel));
303     SuccessOrExit(error = Get<Radio>().SetTransmitPower(mTxPower));
304     otPlatDiagModeSet(true);
305     mStats.Clear();
306     snprintf(aOutput, aOutputMaxLen, "start diagnostics mode\r\nstatus 0x%02x\r\n", error);
307 
308 exit:
309     AppendErrorResult(error, aOutput, aOutputMaxLen);
310     return error;
311 }
312 
313 Error Diags::ProcessStats(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
314 {
315     Error error = kErrorNone;
316 
317     VerifyOrExit(otPlatDiagModeGet(), error = kErrorInvalidState);
318 
319     if ((aArgsLength == 1) && (strcmp(aArgs[0], "clear") == 0))
320     {
321         mStats.Clear();
322         snprintf(aOutput, aOutputMaxLen, "stats cleared\r\n");
323     }
324     else
325     {
326         VerifyOrExit(aArgsLength == 0, error = kErrorInvalidArgs);
327         snprintf(aOutput, aOutputMaxLen,
328                  "received packets: %d\r\nsent packets: %d\r\n"
329                  "first received packet: rssi=%d, lqi=%d\r\n"
330                  "last received packet: rssi=%d, lqi=%d\r\n",
331                  static_cast<int>(mStats.mReceivedPackets), static_cast<int>(mStats.mSentPackets),
332                  static_cast<int>(mStats.mFirstRssi), static_cast<int>(mStats.mFirstLqi),
333                  static_cast<int>(mStats.mLastRssi), static_cast<int>(mStats.mLastLqi));
334     }
335 
336 exit:
337     AppendErrorResult(error, aOutput, aOutputMaxLen);
338     return error;
339 }
340 
341 Error Diags::ProcessStop(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
342 {
343     OT_UNUSED_VARIABLE(aArgsLength);
344     OT_UNUSED_VARIABLE(aArgs);
345 
346     Error error = kErrorNone;
347 
348     VerifyOrExit(otPlatDiagModeGet(), error = kErrorInvalidState);
349 
350     otPlatAlarmMilliStop(&GetInstance());
351     otPlatDiagModeSet(false);
352     Get<Radio>().SetPromiscuous(false);
353 
354     snprintf(aOutput, aOutputMaxLen,
355              "received packets: %d\r\nsent packets: %d\r\n"
356              "first received packet: rssi=%d, lqi=%d\r\n"
357              "last received packet: rssi=%d, lqi=%d\r\n"
358              "\nstop diagnostics mode\r\nstatus 0x%02x\r\n",
359              static_cast<int>(mStats.mReceivedPackets), static_cast<int>(mStats.mSentPackets),
360              static_cast<int>(mStats.mFirstRssi), static_cast<int>(mStats.mFirstLqi),
361              static_cast<int>(mStats.mLastRssi), static_cast<int>(mStats.mLastLqi), error);
362 
363 exit:
364     AppendErrorResult(error, aOutput, aOutputMaxLen);
365     return error;
366 }
367 
368 void Diags::TransmitPacket(void)
369 {
370     mTxPacket->mLength  = mTxLen;
371     mTxPacket->mChannel = mChannel;
372 
373     for (uint8_t i = 0; i < mTxLen; i++)
374     {
375         mTxPacket->mPsdu[i] = i;
376     }
377 
378     IgnoreError(Get<Radio>().Transmit(*static_cast<Mac::TxFrame *>(mTxPacket)));
379 }
380 
381 Error Diags::ProcessRadio(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
382 {
383     Error error = kErrorInvalidArgs;
384 
385     VerifyOrExit(otPlatDiagModeGet(), error = kErrorInvalidState);
386     VerifyOrExit(aArgsLength > 0, error = kErrorInvalidArgs);
387 
388     if (strcmp(aArgs[0], "sleep") == 0)
389     {
390         SuccessOrExit(error = Get<Radio>().Sleep());
391         snprintf(aOutput, aOutputMaxLen, "set radio from receive to sleep \r\nstatus 0x%02x\r\n", error);
392     }
393     else if (strcmp(aArgs[0], "receive") == 0)
394     {
395         SuccessOrExit(error = Get<Radio>().Receive(mChannel));
396         SuccessOrExit(error = Get<Radio>().SetTransmitPower(mTxPower));
397         otPlatDiagChannelSet(mChannel);
398         otPlatDiagTxPowerSet(mTxPower);
399 
400         snprintf(aOutput, aOutputMaxLen, "set radio from sleep to receive on channel %d\r\nstatus 0x%02x\r\n", mChannel,
401                  error);
402     }
403     else if (strcmp(aArgs[0], "state") == 0)
404     {
405         otRadioState state = Get<Radio>().GetState();
406 
407         error = kErrorNone;
408 
409         switch (state)
410         {
411         case OT_RADIO_STATE_DISABLED:
412             snprintf(aOutput, aOutputMaxLen, "disabled\r\n");
413             break;
414 
415         case OT_RADIO_STATE_SLEEP:
416             snprintf(aOutput, aOutputMaxLen, "sleep\r\n");
417             break;
418 
419         case OT_RADIO_STATE_RECEIVE:
420             snprintf(aOutput, aOutputMaxLen, "receive\r\n");
421             break;
422 
423         case OT_RADIO_STATE_TRANSMIT:
424             snprintf(aOutput, aOutputMaxLen, "transmit\r\n");
425             break;
426 
427         default:
428             snprintf(aOutput, aOutputMaxLen, "invalid\r\n");
429             break;
430         }
431     }
432 
433 exit:
434     AppendErrorResult(error, aOutput, aOutputMaxLen);
435     return error;
436 }
437 
438 extern "C" void otPlatDiagAlarmFired(otInstance *aInstance)
439 {
440     Instance *instance = static_cast<Instance *>(aInstance);
441 
442     instance->Get<Diags>().AlarmFired();
443 }
444 
445 void Diags::AlarmFired(void)
446 {
447     if (mRepeatActive)
448     {
449         uint32_t now = otPlatAlarmMilliGetNow();
450 
451         TransmitPacket();
452         otPlatAlarmMilliStartAt(&GetInstance(), now, mTxPeriod);
453     }
454     else
455     {
456         otPlatDiagAlarmCallback(&GetInstance());
457     }
458 }
459 
460 void Diags::ReceiveDone(otRadioFrame *aFrame, Error aError)
461 {
462     if (aError == kErrorNone)
463     {
464         // for sensitivity test, only record the rssi and lqi for the first and last packet
465         if (mStats.mReceivedPackets == 0)
466         {
467             mStats.mFirstRssi = aFrame->mInfo.mRxInfo.mRssi;
468             mStats.mFirstLqi  = aFrame->mInfo.mRxInfo.mLqi;
469         }
470 
471         mStats.mLastRssi = aFrame->mInfo.mRxInfo.mRssi;
472         mStats.mLastLqi  = aFrame->mInfo.mRxInfo.mLqi;
473 
474         mStats.mReceivedPackets++;
475     }
476 
477     otPlatDiagRadioReceived(&GetInstance(), aFrame, aError);
478 }
479 
480 void Diags::TransmitDone(Error aError)
481 {
482     if (aError == kErrorNone)
483     {
484         mStats.mSentPackets++;
485 
486         if (mTxPackets > 1)
487         {
488             mTxPackets--;
489         }
490         else
491         {
492             ExitNow();
493         }
494     }
495 
496     VerifyOrExit(!mRepeatActive);
497     TransmitPacket();
498 
499 exit:
500     return;
501 }
502 
503 #endif // OPENTHREAD_RADIO
504 
AppendErrorResult(Error aError,char * aOutput,size_t aOutputMaxLen)505 void Diags::AppendErrorResult(Error aError, char *aOutput, size_t aOutputMaxLen)
506 {
507     if (aError != kErrorNone)
508     {
509         snprintf(aOutput, aOutputMaxLen, "failed\r\nstatus %#x\r\n", aError);
510     }
511 }
512 
ParseLong(char * aString,long & aLong)513 Error Diags::ParseLong(char *aString, long &aLong)
514 {
515     char *endptr;
516     aLong = strtol(aString, &endptr, 0);
517     return (*endptr == '\0') ? kErrorNone : kErrorParse;
518 }
519 
ParseCmd(char * aString,uint8_t & aArgsLength,char * aArgs[])520 Error Diags::ParseCmd(char *aString, uint8_t &aArgsLength, char *aArgs[])
521 {
522     Error                     error;
523     Utils::CmdLineParser::Arg args[kMaxArgs + 1];
524 
525     SuccessOrExit(error = Utils::CmdLineParser::ParseCmd(aString, args));
526     aArgsLength = Utils::CmdLineParser::Arg::GetArgsLength(args);
527     Utils::CmdLineParser::Arg::CopyArgsToStringArray(args, aArgs);
528 
529 exit:
530     return error;
531 }
532 
ProcessLine(const char * aString,char * aOutput,size_t aOutputMaxLen)533 void Diags::ProcessLine(const char *aString, char *aOutput, size_t aOutputMaxLen)
534 {
535     constexpr uint16_t kMaxCommandBuffer = OPENTHREAD_CONFIG_DIAG_CMD_LINE_BUFFER_SIZE;
536 
537     Error   error = kErrorNone;
538     char    buffer[kMaxCommandBuffer];
539     char *  args[kMaxArgs];
540     uint8_t argCount = 0;
541 
542     VerifyOrExit(StringLength(aString, kMaxCommandBuffer) < kMaxCommandBuffer, error = kErrorNoBufs);
543 
544     strcpy(buffer, aString);
545     error = ParseCmd(buffer, argCount, args);
546 
547 exit:
548 
549     switch (error)
550     {
551     case kErrorNone:
552         aOutput[0] = '\0'; // In case there is no output.
553         IgnoreError(ProcessCmd(argCount, &args[0], aOutput, aOutputMaxLen));
554         break;
555 
556     case kErrorNoBufs:
557         snprintf(aOutput, aOutputMaxLen, "failed: command string too long\r\n");
558         break;
559 
560     case kErrorInvalidArgs:
561         snprintf(aOutput, aOutputMaxLen, "failed: command string contains too many arguments\r\n");
562         break;
563 
564     default:
565         snprintf(aOutput, aOutputMaxLen, "failed to parse command string\r\n");
566         break;
567     }
568 }
569 
ProcessCmd(uint8_t aArgsLength,char * aArgs[],char * aOutput,size_t aOutputMaxLen)570 Error Diags::ProcessCmd(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
571 {
572     Error error = kErrorNone;
573 
574     // This `rcp` command is for debugging and testing only, building only when NDEBUG is not defined
575     // so that it will be excluded from release build.
576 #if !defined(NDEBUG) && defined(OPENTHREAD_RADIO)
577     if (aArgsLength > 0 && !strcmp(aArgs[0], "rcp"))
578     {
579         aArgs++;
580         aArgsLength--;
581     }
582 #endif
583 
584     if (aArgsLength == 0)
585     {
586         snprintf(aOutput, aOutputMaxLen, "diagnostics mode is %s\r\n", otPlatDiagModeGet() ? "enabled" : "disabled");
587         ExitNow();
588     }
589     else
590     {
591         aOutput[0] = '\0';
592     }
593 
594     for (const Command &command : sCommands)
595     {
596         if (strcmp(aArgs[0], command.mName) == 0)
597         {
598             error = (this->*command.mCommand)(aArgsLength - 1, (aArgsLength > 1) ? &aArgs[1] : nullptr, aOutput,
599                                               aOutputMaxLen);
600             ExitNow();
601         }
602     }
603 
604     // more platform specific features will be processed under platform layer
605     error = otPlatDiagProcess(&GetInstance(), aArgsLength, aArgs, aOutput, aOutputMaxLen);
606 
607 exit:
608     // Add more platform specific diagnostics features here.
609     if (error == kErrorInvalidCommand && aArgsLength > 1)
610     {
611         snprintf(aOutput, aOutputMaxLen, "diag feature '%s' is not supported\r\n", aArgs[0]);
612     }
613 
614     return error;
615 }
616 
IsEnabled(void)617 bool Diags::IsEnabled(void)
618 {
619     return otPlatDiagModeGet();
620 }
621 
622 } // namespace FactoryDiags
623 } // namespace ot
624 
625 #endif // OPENTHREAD_CONFIG_DIAG_ENABLE
626