Apply Upstream code (2021-03-15)
[platform/upstream/connectedhomeip.git] / examples / shell / shell_common / cmd_ping.cpp
1 /*
2  *
3  *    Copyright (c) 2021 Project CHIP Authors
4  *
5  *    Licensed under the Apache License, Version 2.0 (the "License");
6  *    you may not use this file except in compliance with the License.
7  *    You may obtain a copy of the License at
8  *
9  *        http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *    Unless required by applicable law or agreed to in writing, software
12  *    distributed under the License is distributed on an "AS IS" BASIS,
13  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *    See the License for the specific language governing permissions and
15  *    limitations under the License.
16  */
17
18 #include <errno.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21
22 #include <core/CHIPCore.h>
23 #include <messaging/ExchangeMgr.h>
24 #include <platform/CHIPDeviceLayer.h>
25 #include <protocols/echo/Echo.h>
26 #include <shell/shell.h>
27 #include <support/CodeUtils.h>
28 #include <support/ErrorStr.h>
29 #include <system/SystemPacketBuffer.h>
30 #include <transport/PASESession.h>
31 #include <transport/SecureSessionMgr.h>
32 #include <transport/raw/TCP.h>
33 #include <transport/raw/UDP.h>
34
35 #include <ChipShellCollection.h>
36
37 using namespace chip;
38 using namespace Shell;
39 using namespace Logging;
40
41 constexpr size_t kMaxTcpActiveConnectionCount = 4;
42 constexpr size_t kMaxTcpPendingPackets        = 4;
43 constexpr size_t kNetworkSleepTimeMsecs       = (100 * 1000);
44 constexpr size_t kDecimalDigitsForUint64      = 20;
45
46 namespace {
47
48 class PingArguments
49 {
50 public:
51     void Reset()
52     {
53         mMaxEchoCount       = 3;
54         mEchoInterval       = 1000;
55         mLastEchoTime       = 0;
56         mEchoCount          = 0;
57         mEchoRespCount      = 0;
58         mWaitingForEchoResp = false;
59         mUsingTCP           = false;
60         mUsingCRMP          = true;
61         mEchoPort           = CHIP_PORT;
62     }
63
64     uint64_t GetLastEchoTime() const { return mLastEchoTime; }
65     void SetLastEchoTime(uint64_t value) { mLastEchoTime = value; }
66
67     uint64_t GetEchoCount() const { return mEchoCount; }
68     void SetEchoCount(uint64_t value) { mEchoCount = value; }
69     void IncrementEchoCount() { mEchoCount++; }
70
71     uint64_t GetEchoRespCount() const { return mEchoRespCount; }
72     void SetEchoRespCount(uint64_t value) { mEchoRespCount = value; }
73     void IncrementEchoRespCount() { mEchoRespCount++; }
74
75     uint32_t GetMaxEchoCount() const { return mMaxEchoCount; }
76     void SetMaxEchoCount(uint32_t id) { mMaxEchoCount = id; }
77
78     uint32_t GetEchoInterval() const { return mEchoInterval; }
79     void SetEchoInterval(uint32_t value) { mEchoInterval = value; }
80
81     uint16_t GetEchoPort() const { return mEchoPort; }
82     void SetEchoPort(uint16_t value) { mEchoPort = value; }
83
84     bool IsWaitingForEchoResp() const { return mWaitingForEchoResp; }
85     void SetWaitingForEchoResp(bool value) { mWaitingForEchoResp = value; }
86
87     bool IsUsingTCP() const { return mUsingTCP; }
88     void SetUsingTCP(bool value) { mUsingTCP = value; }
89
90     bool IsUsingCRMP() const { return mUsingCRMP; }
91     void SetUsingCRMP(bool value) { mUsingCRMP = value; }
92
93 private:
94     // The last time a echo request was attempted to be sent.
95     uint64_t mLastEchoTime;
96
97     // Count of the number of echo requests sent.
98     uint64_t mEchoCount;
99
100     // Count of the number of echo responses received.
101     uint64_t mEchoRespCount;
102
103     // Max value for the number of echo requests sent.
104     uint32_t mMaxEchoCount;
105
106     // The CHIP Echo interval time in milliseconds.
107     uint32_t mEchoInterval;
108
109     uint16_t mEchoPort;
110
111     // True, if the echo client is waiting for an echo response
112     // after sending an echo request, false otherwise.
113     bool mWaitingForEchoResp;
114
115     bool mUsingTCP;
116     bool mUsingCRMP;
117 } gPingArguments;
118
119 constexpr Transport::AdminId gAdminId = 0;
120
121 Protocols::Echo::EchoClient gEchoClient;
122 TransportMgr<Transport::UDP> gUDPManager;
123 TransportMgr<Transport::TCP<kMaxTcpActiveConnectionCount, kMaxTcpPendingPackets>> gTCPManager;
124 Messaging::ExchangeManager gExchangeManager;
125 SecureSessionMgr gSessionManager;
126 Inet::IPAddress gDestAddr;
127
128 bool EchoIntervalExpired(void)
129 {
130     uint64_t now = System::Timer::GetCurrentEpoch();
131
132     return (now >= gPingArguments.GetLastEchoTime() + gPingArguments.GetEchoInterval());
133 }
134
135 CHIP_ERROR SendEchoRequest(streamer_t * stream)
136 {
137     CHIP_ERROR err = CHIP_NO_ERROR;
138
139     Messaging::SendFlags sendFlags;
140     const char kRequestFormat[] = "Echo Message %" PRIu64 "\n";
141     char requestData[(sizeof kRequestFormat) + kDecimalDigitsForUint64];
142     snprintf(requestData, sizeof requestData, kRequestFormat, gPingArguments.GetEchoCount());
143     System::PacketBufferHandle payloadBuf = MessagePacketBuffer::NewWithData(requestData, strlen(requestData));
144
145     if (gPingArguments.IsUsingCRMP())
146     {
147         sendFlags.Set(Messaging::SendMessageFlags::kNone);
148     }
149     else
150     {
151         sendFlags.Set(Messaging::SendMessageFlags::kNoAutoRequestAck);
152     }
153
154     if (payloadBuf.IsNull())
155     {
156         streamer_printf(stream, "Unable to allocate packet buffer\n");
157         return CHIP_ERROR_NO_MEMORY;
158     }
159
160     gPingArguments.SetLastEchoTime(System::Timer::GetCurrentEpoch());
161
162     streamer_printf(stream, "\nSend echo request message to Node: %" PRIu64 "\n", kTestDeviceNodeId);
163
164     err = gEchoClient.SendEchoRequest(std::move(payloadBuf), sendFlags);
165
166     if (err == CHIP_NO_ERROR)
167     {
168         gPingArguments.SetWaitingForEchoResp(true);
169         gPingArguments.IncrementEchoCount();
170     }
171     else
172     {
173         streamer_printf(stream, "Send echo request failed, err: %s\n", ErrorStr(err));
174     }
175
176     return err;
177 }
178
179 CHIP_ERROR EstablishSecureSession(streamer_t * stream)
180 {
181     CHIP_ERROR err = CHIP_NO_ERROR;
182
183     Optional<Transport::PeerAddress> peerAddr;
184     SecurePairingUsingTestSecret * testSecurePairingSecret = chip::Platform::New<SecurePairingUsingTestSecret>();
185     VerifyOrExit(testSecurePairingSecret != nullptr, err = CHIP_ERROR_NO_MEMORY);
186
187     if (gPingArguments.IsUsingTCP())
188     {
189         peerAddr = Optional<Transport::PeerAddress>::Value(Transport::PeerAddress::TCP(gDestAddr, gPingArguments.GetEchoPort()));
190     }
191     else
192     {
193         peerAddr = Optional<Transport::PeerAddress>::Value(
194             Transport::PeerAddress::UDP(gDestAddr, gPingArguments.GetEchoPort(), INET_NULL_INTERFACEID));
195     }
196
197     // Attempt to connect to the peer.
198     err = gSessionManager.NewPairing(peerAddr, kTestDeviceNodeId, testSecurePairingSecret,
199                                      SecureSessionMgr::PairingDirection::kInitiator, gAdminId);
200
201 exit:
202     if (err != CHIP_NO_ERROR)
203     {
204         streamer_printf(stream, "Establish secure session failed, err: %s\n", ErrorStr(err));
205         gPingArguments.SetLastEchoTime(System::Timer::GetCurrentEpoch());
206     }
207     else
208     {
209         streamer_printf(stream, "Establish secure session succeeded\n");
210     }
211
212     return err;
213 }
214
215 void HandleEchoResponseReceived(Messaging::ExchangeContext * ec, System::PacketBufferHandle payload)
216 {
217     uint32_t respTime    = System::Timer::GetCurrentEpoch();
218     uint32_t transitTime = respTime - gPingArguments.GetLastEchoTime();
219     streamer_t * sout    = streamer_get();
220
221     gPingArguments.SetWaitingForEchoResp(false);
222     gPingArguments.IncrementEchoRespCount();
223
224     streamer_printf(sout, "Echo Response: %" PRIu64 "/%" PRIu64 "(%.2f%%) len=%u time=%.3fms\n", gPingArguments.GetEchoRespCount(),
225                     gPingArguments.GetEchoCount(),
226                     static_cast<double>(gPingArguments.GetEchoRespCount()) * 100 / gPingArguments.GetEchoCount(),
227                     payload->DataLength(), static_cast<double>(transitTime) / 1000);
228 }
229
230 void DriveIO(streamer_t * stream)
231 {
232     struct timeval sleepTime;
233     fd_set readFDs, writeFDs, exceptFDs;
234     int numFDs = 0;
235     int selectRes;
236
237     sleepTime.tv_sec  = 0;
238     sleepTime.tv_usec = kNetworkSleepTimeMsecs;
239
240     FD_ZERO(&readFDs);
241     FD_ZERO(&writeFDs);
242     FD_ZERO(&exceptFDs);
243
244     if (chip::DeviceLayer::SystemLayer.State() == chip::System::kLayerState_Initialized)
245         chip::DeviceLayer::SystemLayer.PrepareSelect(numFDs, &readFDs, &writeFDs, &exceptFDs, sleepTime);
246
247     if (chip::DeviceLayer::InetLayer.State == chip::Inet::InetLayer::kState_Initialized)
248         chip::DeviceLayer::InetLayer.PrepareSelect(numFDs, &readFDs, &writeFDs, &exceptFDs, sleepTime);
249
250     selectRes = select(numFDs, &readFDs, &writeFDs, &exceptFDs, &sleepTime);
251     if (selectRes < 0)
252     {
253         streamer_printf(stream, "Select failed: %s\n", chip::ErrorStr(chip::System::MapErrorPOSIX(errno)));
254         return;
255     }
256
257     if (chip::DeviceLayer::SystemLayer.State() == chip::System::kLayerState_Initialized)
258     {
259         chip::DeviceLayer::SystemLayer.HandleSelectResult(selectRes, &readFDs, &writeFDs, &exceptFDs);
260     }
261
262     if (chip::DeviceLayer::InetLayer.State == chip::Inet::InetLayer::kState_Initialized)
263     {
264         chip::DeviceLayer::InetLayer.HandleSelectResult(selectRes, &readFDs, &writeFDs, &exceptFDs);
265     }
266 }
267
268 void StartPinging(streamer_t * stream, char * destination)
269 {
270     CHIP_ERROR err = CHIP_NO_ERROR;
271
272     Transport::AdminPairingTable admins;
273     Transport::AdminPairingInfo * adminInfo = nullptr;
274     uint32_t maxEchoCount                   = 0;
275
276     if (!Inet::IPAddress::FromString(destination, gDestAddr))
277     {
278         streamer_printf(stream, "Invalid Echo Server IP address: %s\n", destination);
279         ExitNow(err = CHIP_ERROR_INVALID_ARGUMENT);
280     }
281
282     adminInfo = admins.AssignAdminId(gAdminId, kTestControllerNodeId);
283     VerifyOrExit(adminInfo != nullptr, err = CHIP_ERROR_NO_MEMORY);
284
285     err = gTCPManager.Init(Transport::TcpListenParameters(&DeviceLayer::InetLayer)
286                                .SetAddressType(Inet::kIPAddressType_IPv4)
287                                .SetListenPort(gPingArguments.GetEchoPort() + 1));
288     VerifyOrExit(err == CHIP_NO_ERROR, streamer_printf(stream, "Failed to init TCP manager error: %s\r\n", ErrorStr(err)));
289
290     err = gUDPManager.Init(Transport::UdpListenParameters(&DeviceLayer::InetLayer)
291                                .SetAddressType(Inet::kIPAddressType_IPv4)
292                                .SetListenPort(gPingArguments.GetEchoPort() + 1));
293     VerifyOrExit(err == CHIP_NO_ERROR, streamer_printf(stream, "Failed to init UDP manager error: %s\r\n", ErrorStr(err)));
294
295     if (gPingArguments.IsUsingTCP())
296     {
297         err = gSessionManager.Init(kTestControllerNodeId, &DeviceLayer::SystemLayer, &gTCPManager, &admins);
298         SuccessOrExit(err);
299
300         err = gExchangeManager.Init(kTestControllerNodeId, &gTCPManager, &gSessionManager);
301         SuccessOrExit(err);
302     }
303     else
304     {
305         err = gSessionManager.Init(kTestControllerNodeId, &DeviceLayer::SystemLayer, &gUDPManager, &admins);
306         SuccessOrExit(err);
307
308         err = gExchangeManager.Init(kTestControllerNodeId, &gUDPManager, &gSessionManager);
309         SuccessOrExit(err);
310     }
311
312     // Start the CHIP connection to the CHIP echo responder.
313     err = EstablishSecureSession(stream);
314     SuccessOrExit(err);
315
316     // TODO: temprary create a SecureSessionHandle from node id to unblock end-to-end test. Complete solution is tracked in PR:4451
317     err = gEchoClient.Init(&gExchangeManager, { kTestDeviceNodeId, 0, gAdminId });
318     SuccessOrExit(err);
319
320     // Arrange to get a callback whenever an Echo Response is received.
321     gEchoClient.SetEchoResponseReceived(HandleEchoResponseReceived);
322
323     maxEchoCount = gPingArguments.GetMaxEchoCount();
324
325     // Connection has been established. Now send the EchoRequests.
326     for (unsigned int i = 0; i < maxEchoCount; i++)
327     {
328         err = SendEchoRequest(stream);
329         if (err != CHIP_NO_ERROR)
330         {
331             streamer_printf(stream, "Send request failed: %s\n", ErrorStr(err));
332             break;
333         }
334
335         // Wait for response until the Echo interval.
336         while (!EchoIntervalExpired())
337         {
338             DriveIO(stream);
339         }
340
341         // Check if expected response was received.
342         if (gPingArguments.IsWaitingForEchoResp())
343         {
344             streamer_printf(stream, "No response received\n");
345             gPingArguments.SetWaitingForEchoResp(false);
346         }
347     }
348
349     gEchoClient.Shutdown();
350     gExchangeManager.Shutdown();
351     gSessionManager.Shutdown();
352
353 exit:
354     if ((err != CHIP_NO_ERROR))
355     {
356         streamer_printf(stream, "Ping failed with error: %s\n", ErrorStr(err));
357     }
358 }
359
360 void PrintUsage(streamer_t * stream)
361 {
362     streamer_printf(stream, "Usage: ping [options] <destination>\n\nOptions:\n");
363
364     streamer_printf(stream,
365                     "  -h              print help information\n"
366                     "  -u              use UDP (default)\n"
367                     "  -t              use TCP\n"
368                     "  -p  <port>      echo server port\n"
369                     "  -i  <interval>  ping interval time in seconds\n"
370                     "  -c  <count>     stop after <count> replies\n"
371                     "  -r  <1|0>       enalbe/disable CRMP\n");
372 }
373
374 int cmd_ping(int argc, char ** argv)
375 {
376     streamer_t * sout = streamer_get();
377     int ret           = 0;
378     int optIndex      = 0;
379
380     gPingArguments.Reset();
381
382     while (optIndex < argc && argv[optIndex][0] == '-')
383     {
384         switch (argv[optIndex][1])
385         {
386         case 'h':
387             PrintUsage(sout);
388             return 0;
389         case 'u':
390             gPingArguments.SetUsingTCP(false);
391             break;
392         case 't':
393             gPingArguments.SetUsingTCP(true);
394             break;
395         case 'i':
396             if (++optIndex >= argc || argv[optIndex][0] == '-')
397             {
398                 streamer_printf(sout, "Invalid argument specified for -i\n");
399                 return -1;
400             }
401             else
402             {
403                 gPingArguments.SetEchoInterval(atol(argv[optIndex]) * 1000);
404             }
405             break;
406         case 'c':
407             if (++optIndex >= argc || argv[optIndex][0] == '-')
408             {
409                 streamer_printf(sout, "Invalid argument specified for -c\n");
410                 return -1;
411             }
412             else
413             {
414                 gPingArguments.SetMaxEchoCount(atol(argv[optIndex]));
415             }
416             break;
417         case 'p':
418             if (++optIndex >= argc || argv[optIndex][0] == '-')
419             {
420                 streamer_printf(sout, "Invalid argument specified for -c\n");
421                 return -1;
422             }
423             else
424             {
425                 gPingArguments.SetEchoPort(atol(argv[optIndex]));
426             }
427             break;
428         case 'r':
429             if (++optIndex >= argc || argv[optIndex][0] == '-')
430             {
431                 streamer_printf(sout, "Invalid argument specified for -r\n");
432                 return -1;
433             }
434             else
435             {
436                 int arg = atoi(argv[optIndex]);
437
438                 if (arg == 0)
439                 {
440                     gPingArguments.SetUsingCRMP(false);
441                 }
442                 else if (arg == 1)
443                 {
444                     gPingArguments.SetUsingCRMP(true);
445                 }
446                 else
447                 {
448                     ret = -1;
449                 }
450             }
451             break;
452         default:
453             ret = -1;
454         }
455
456         optIndex++;
457     }
458
459     if (optIndex >= argc)
460     {
461         streamer_printf(sout, "Missing IP address\n");
462         ret = -1;
463     }
464
465     if (ret == 0)
466     {
467         streamer_printf(sout, "IP address: %s\n", argv[optIndex]);
468         StartPinging(sout, argv[optIndex]);
469     }
470
471     return ret;
472 }
473
474 } // namespace
475
476 static shell_command_t cmds_ping[] = {
477     { &cmd_ping, "ping", "Using Echo Protocol to measure packet loss across network paths" },
478 };
479
480 void cmd_ping_init()
481 {
482     shell_register(cmds_ping, ArraySize(cmds_ping));
483 }