1 /*
2  *  Copyright (c) 2024, 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 CLI interpreter for Ping Sender function.
32  */
33 
34 #include "cli_ping.hpp"
35 
36 #include <openthread/ping_sender.h>
37 
38 #include "cli/cli.hpp"
39 #include "cli/cli_utils.hpp"
40 #include "common/code_utils.hpp"
41 
42 #if OPENTHREAD_CONFIG_PING_SENDER_ENABLE
43 
44 namespace ot {
45 namespace Cli {
46 
PingSender(otInstance * aInstance,OutputImplementer & aOutputImplementer)47 PingSender::PingSender(otInstance *aInstance, OutputImplementer &aOutputImplementer)
48     : Utils(aInstance, aOutputImplementer)
49     , mPingIsAsync(false)
50 {
51 }
52 
Process(Arg aArgs[])53 otError PingSender::Process(Arg aArgs[])
54 {
55     otError            error = OT_ERROR_NONE;
56     otPingSenderConfig config;
57     bool               async = false;
58     bool               nat64Synth;
59 
60     /**
61      * @cli ping stop
62      * @code
63      * ping stop
64      * Done
65      * @endcode
66      * @par
67      * Stop sending ICMPv6 Echo Requests.
68      * @sa otPingSenderStop
69      */
70     if (aArgs[0] == "stop")
71     {
72         otPingSenderStop(GetInstancePtr());
73         ExitNow();
74     }
75     else if (aArgs[0] == "async")
76     {
77         async = true;
78         aArgs++;
79     }
80 
81     ClearAllBytes(config);
82 
83     if (aArgs[0] == "-I")
84     {
85         SuccessOrExit(error = aArgs[1].ParseAsIp6Address(config.mSource));
86 
87 #if !OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
88         VerifyOrExit(otIp6HasUnicastAddress(GetInstancePtr(), &config.mSource), error = OT_ERROR_INVALID_ARGS);
89 #endif
90         aArgs += 2;
91     }
92 
93     if (aArgs[0] == "-m")
94     {
95         config.mMulticastLoop = true;
96         aArgs++;
97     }
98 
99     SuccessOrExit(error = ParseToIp6Address(GetInstancePtr(), aArgs[0], config.mDestination, nat64Synth));
100 
101     if (nat64Synth)
102     {
103         OutputFormat("Pinging synthesized IPv6 address: ");
104         OutputIp6AddressLine(config.mDestination);
105     }
106 
107     if (!aArgs[1].IsEmpty())
108     {
109         SuccessOrExit(error = aArgs[1].ParseAsUint16(config.mSize));
110     }
111 
112     if (!aArgs[2].IsEmpty())
113     {
114         SuccessOrExit(error = aArgs[2].ParseAsUint16(config.mCount));
115     }
116 
117     if (!aArgs[3].IsEmpty())
118     {
119         SuccessOrExit(error = ParsePingInterval(aArgs[3], config.mInterval));
120     }
121 
122     if (!aArgs[4].IsEmpty())
123     {
124         SuccessOrExit(error = aArgs[4].ParseAsUint8(config.mHopLimit));
125         config.mAllowZeroHopLimit = (config.mHopLimit == 0);
126     }
127 
128     if (!aArgs[5].IsEmpty())
129     {
130         uint32_t timeout;
131 
132         SuccessOrExit(error = ParsePingInterval(aArgs[5], timeout));
133         VerifyOrExit(timeout <= NumericLimits<uint16_t>::kMax, error = OT_ERROR_INVALID_ARGS);
134         config.mTimeout = static_cast<uint16_t>(timeout);
135     }
136 
137     VerifyOrExit(aArgs[6].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
138 
139     config.mReplyCallback      = PingSender::HandlePingReply;
140     config.mStatisticsCallback = PingSender::HandlePingStatistics;
141     config.mCallbackContext    = this;
142 
143     SuccessOrExit(error = otPingSenderPing(GetInstancePtr(), &config));
144 
145     mPingIsAsync = async;
146 
147     if (!async)
148     {
149         error = OT_ERROR_PENDING;
150     }
151 
152 exit:
153     return error;
154 }
155 
ParsePingInterval(const Arg & aArg,uint32_t & aInterval)156 otError PingSender::ParsePingInterval(const Arg &aArg, uint32_t &aInterval)
157 {
158     otError        error    = OT_ERROR_NONE;
159     const char    *string   = aArg.GetCString();
160     const uint32_t msFactor = 1000;
161     uint32_t       factor   = msFactor;
162 
163     aInterval = 0;
164 
165     while (*string)
166     {
167         if ('0' <= *string && *string <= '9')
168         {
169             // In the case of seconds, change the base of already calculated value.
170             if (factor == msFactor)
171             {
172                 aInterval *= 10;
173             }
174 
175             aInterval += static_cast<uint32_t>(*string - '0') * factor;
176 
177             // In the case of milliseconds, change the multiplier factor.
178             if (factor != msFactor)
179             {
180                 factor /= 10;
181             }
182         }
183         else if (*string == '.')
184         {
185             // Accept only one dot character.
186             VerifyOrExit(factor == msFactor, error = OT_ERROR_INVALID_ARGS);
187 
188             // Start analyzing hundreds of milliseconds.
189             factor /= 10;
190         }
191         else
192         {
193             ExitNow(error = OT_ERROR_INVALID_ARGS);
194         }
195 
196         string++;
197     }
198 
199 exit:
200     return error;
201 }
202 
HandlePingReply(const otPingSenderReply * aReply,void * aContext)203 void PingSender::HandlePingReply(const otPingSenderReply *aReply, void *aContext)
204 {
205     static_cast<PingSender *>(aContext)->HandlePingReply(aReply);
206 }
207 
HandlePingReply(const otPingSenderReply * aReply)208 void PingSender::HandlePingReply(const otPingSenderReply *aReply)
209 {
210     OutputFormat("%u bytes from ", static_cast<uint16_t>(aReply->mSize + sizeof(otIcmp6Header)));
211     OutputIp6Address(aReply->mSenderAddress);
212     OutputLine(": icmp_seq=%u hlim=%u time=%ums", aReply->mSequenceNumber, aReply->mHopLimit, aReply->mRoundTripTime);
213 }
214 
HandlePingStatistics(const otPingSenderStatistics * aStatistics,void * aContext)215 void PingSender::HandlePingStatistics(const otPingSenderStatistics *aStatistics, void *aContext)
216 {
217     static_cast<PingSender *>(aContext)->HandlePingStatistics(aStatistics);
218 }
219 
HandlePingStatistics(const otPingSenderStatistics * aStatistics)220 void PingSender::HandlePingStatistics(const otPingSenderStatistics *aStatistics)
221 {
222     OutputFormat("%u packets transmitted, %u packets received.", aStatistics->mSentCount, aStatistics->mReceivedCount);
223 
224     if ((aStatistics->mSentCount != 0) && !aStatistics->mIsMulticast &&
225         aStatistics->mReceivedCount <= aStatistics->mSentCount)
226     {
227         uint32_t packetLossRate =
228             1000 * (aStatistics->mSentCount - aStatistics->mReceivedCount) / aStatistics->mSentCount;
229 
230         OutputFormat(" Packet loss = %lu.%u%%.", ToUlong(packetLossRate / 10),
231                      static_cast<uint16_t>(packetLossRate % 10));
232     }
233 
234     if (aStatistics->mReceivedCount != 0)
235     {
236         uint32_t avgRoundTripTime = 1000 * aStatistics->mTotalRoundTripTime / aStatistics->mReceivedCount;
237 
238         OutputFormat(" Round-trip min/avg/max = %u/%u.%u/%u ms.", aStatistics->mMinRoundTripTime,
239                      static_cast<uint16_t>(avgRoundTripTime / 1000), static_cast<uint16_t>(avgRoundTripTime % 1000),
240                      aStatistics->mMaxRoundTripTime);
241     }
242 
243     OutputNewLine();
244 
245     if (!mPingIsAsync)
246     {
247         OutputResult(OT_ERROR_NONE);
248     }
249 }
250 
OutputResult(otError aError)251 void PingSender::OutputResult(otError aError) { Interpreter::GetInterpreter().OutputResult(aError); }
252 
253 } // namespace Cli
254 } // namespace ot
255 
256 #endif // OPENTHREAD_CONFIG_PING_SENDER_ENABLE
257