Fix for x86_64 build fail
[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 <lib/core/CHIPCore.h>
23 #include <lib/shell/shell.h>
24 #include <lib/support/CodeUtils.h>
25 #include <lib/support/ErrorStr.h>
26 #include <messaging/ExchangeMgr.h>
27 #include <platform/CHIPDeviceLayer.h>
28 #include <protocols/echo/Echo.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 #if INET_CONFIG_ENABLE_TCP_ENDPOINT
42 constexpr size_t kMaxTcpActiveConnectionCount = 4;
43 constexpr size_t kMaxTcpPendingPackets        = 4;
44 #endif
45 constexpr size_t kMaxPayloadSize = 1280;
46
47 namespace {
48
49 class PingArguments
50 {
51 public:
52     void Reset()
53     {
54         mMaxEchoCount       = 3;
55         mEchoInterval       = 1000;
56         mLastEchoTime       = 0;
57         mEchoCount          = 0;
58         mEchoRespCount      = 0;
59         mEchoReqSize        = 32;
60         mWaitingForEchoResp = false;
61 #if INET_CONFIG_ENABLE_TCP_ENDPOINT
62         mUsingTCP = false;
63 #endif
64         mUsingCRMP = true;
65         mEchoPort  = CHIP_PORT;
66     }
67
68     uint64_t GetLastEchoTime() const { return mLastEchoTime; }
69     void SetLastEchoTime(uint64_t value) { mLastEchoTime = value; }
70
71     uint64_t GetEchoCount() const { return mEchoCount; }
72     void SetEchoCount(uint64_t value) { mEchoCount = value; }
73     void IncrementEchoCount() { mEchoCount++; }
74
75     uint64_t GetEchoRespCount() const { return mEchoRespCount; }
76     void SetEchoRespCount(uint64_t value) { mEchoRespCount = value; }
77     void IncrementEchoRespCount() { mEchoRespCount++; }
78
79     uint32_t GetMaxEchoCount() const { return mMaxEchoCount; }
80     void SetMaxEchoCount(uint32_t id) { mMaxEchoCount = id; }
81
82     uint32_t GetEchoInterval() const { return mEchoInterval; }
83     void SetEchoInterval(uint32_t value) { mEchoInterval = value; }
84
85     uint32_t GetEchoReqSize() const { return mEchoReqSize; }
86     void SetEchoReqSize(uint32_t value) { mEchoReqSize = value; }
87
88     uint16_t GetEchoPort() const { return mEchoPort; }
89     void SetEchoPort(uint16_t value) { mEchoPort = value; }
90
91     bool IsWaitingForEchoResp() const { return mWaitingForEchoResp; }
92     void SetWaitingForEchoResp(bool value) { mWaitingForEchoResp = value; }
93
94 #if INET_CONFIG_ENABLE_TCP_ENDPOINT
95     bool IsUsingTCP() const { return mUsingTCP; }
96     void SetUsingTCP(bool value) { mUsingTCP = value; }
97 #endif
98
99     bool IsUsingCRMP() const { return mUsingCRMP; }
100     void SetUsingCRMP(bool value) { mUsingCRMP = value; }
101
102 private:
103     // The last time a echo request was attempted to be sent.
104     uint64_t mLastEchoTime;
105
106     // Count of the number of echo requests sent.
107     uint64_t mEchoCount;
108
109     // Count of the number of echo responses received.
110     uint64_t mEchoRespCount;
111
112     // The CHIP Echo request payload size in bytes.
113     uint32_t mEchoReqSize;
114
115     // Max value for the number of echo requests sent.
116     uint32_t mMaxEchoCount;
117
118     // The CHIP Echo interval time in milliseconds.
119     uint32_t mEchoInterval;
120
121     uint16_t mEchoPort;
122
123     // True, if the echo client is waiting for an echo response
124     // after sending an echo request, false otherwise.
125     bool mWaitingForEchoResp;
126
127 #if INET_CONFIG_ENABLE_TCP_ENDPOINT
128     bool mUsingTCP;
129 #endif
130
131     bool mUsingCRMP;
132 } gPingArguments;
133
134 constexpr Transport::AdminId gAdminId = 0;
135
136 Protocols::Echo::EchoClient gEchoClient;
137
138 TransportMgr<Transport::UDP> gUDPManager;
139
140 #if INET_CONFIG_ENABLE_TCP_ENDPOINT
141 TransportMgr<Transport::TCP<kMaxTcpActiveConnectionCount, kMaxTcpPendingPackets>> gTCPManager;
142 #endif
143
144 Messaging::ExchangeManager gExchangeManager;
145 SecureSessionMgr gSessionManager;
146 Inet::IPAddress gDestAddr;
147
148 bool EchoIntervalExpired(void)
149 {
150     uint64_t now = System::Timer::GetCurrentEpoch();
151
152     return (now >= gPingArguments.GetLastEchoTime() + gPingArguments.GetEchoInterval());
153 }
154
155 CHIP_ERROR SendEchoRequest(streamer_t * stream)
156 {
157     CHIP_ERROR err = CHIP_NO_ERROR;
158
159     Messaging::SendFlags sendFlags;
160     System::PacketBufferHandle payloadBuf;
161     char * requestData = nullptr;
162
163     uint32_t size = gPingArguments.GetEchoReqSize();
164     VerifyOrExit(size <= kMaxPayloadSize, err = CHIP_ERROR_INVALID_MESSAGE_LENGTH);
165
166     requestData = static_cast<char *>(chip::Platform::MemoryAlloc(size));
167     VerifyOrExit(requestData != nullptr, err = CHIP_ERROR_NO_MEMORY);
168
169     snprintf(requestData, size, "Echo Message %" PRIu64 "\n", gPingArguments.GetEchoCount());
170
171     payloadBuf = MessagePacketBuffer::NewWithData(requestData, size);
172     VerifyOrExit(!payloadBuf.IsNull(), err = CHIP_ERROR_NO_MEMORY);
173
174     if (gPingArguments.IsUsingCRMP())
175     {
176         sendFlags.Set(Messaging::SendMessageFlags::kNone);
177     }
178     else
179     {
180         sendFlags.Set(Messaging::SendMessageFlags::kNoAutoRequestAck);
181     }
182
183     gPingArguments.SetLastEchoTime(System::Timer::GetCurrentEpoch());
184
185     streamer_printf(stream, "\nSend echo request message with payload size: %d bytes to Node: %" PRIu64 "\n", size,
186                     kTestDeviceNodeId);
187
188     err = gEchoClient.SendEchoRequest(std::move(payloadBuf), sendFlags);
189
190     if (err == CHIP_NO_ERROR)
191     {
192         gPingArguments.SetWaitingForEchoResp(true);
193         gPingArguments.IncrementEchoCount();
194     }
195
196 exit:
197     if (requestData != nullptr)
198     {
199         chip::Platform::MemoryFree(requestData);
200     }
201
202     if (err != CHIP_NO_ERROR)
203     {
204         streamer_printf(stream, "Send echo request failed, err: %s\n", ErrorStr(err));
205     }
206
207     return err;
208 }
209
210 CHIP_ERROR EstablishSecureSession(streamer_t * stream)
211 {
212     CHIP_ERROR err = CHIP_NO_ERROR;
213
214     Optional<Transport::PeerAddress> peerAddr;
215     SecurePairingUsingTestSecret * testSecurePairingSecret = chip::Platform::New<SecurePairingUsingTestSecret>();
216     VerifyOrExit(testSecurePairingSecret != nullptr, err = CHIP_ERROR_NO_MEMORY);
217
218 #if INET_CONFIG_ENABLE_TCP_ENDPOINT
219     if (gPingArguments.IsUsingTCP())
220     {
221         peerAddr = Optional<Transport::PeerAddress>::Value(Transport::PeerAddress::TCP(gDestAddr, gPingArguments.GetEchoPort()));
222     }
223     else
224 #endif
225     {
226         peerAddr = Optional<Transport::PeerAddress>::Value(
227             Transport::PeerAddress::UDP(gDestAddr, gPingArguments.GetEchoPort(), INET_NULL_INTERFACEID));
228     }
229
230     // Attempt to connect to the peer.
231     err = gSessionManager.NewPairing(peerAddr, kTestDeviceNodeId, testSecurePairingSecret,
232                                      SecureSessionMgr::PairingDirection::kInitiator, gAdminId);
233
234 exit:
235     if (err != CHIP_NO_ERROR)
236     {
237         streamer_printf(stream, "Establish secure session failed, err: %s\n", ErrorStr(err));
238         gPingArguments.SetLastEchoTime(System::Timer::GetCurrentEpoch());
239     }
240     else
241     {
242         streamer_printf(stream, "Establish secure session succeeded\n");
243     }
244
245     return err;
246 }
247
248 void HandleEchoResponseReceived(Messaging::ExchangeContext * ec, System::PacketBufferHandle payload)
249 {
250     uint32_t respTime    = System::Timer::GetCurrentEpoch();
251     uint32_t transitTime = respTime - gPingArguments.GetLastEchoTime();
252     streamer_t * sout    = streamer_get();
253
254     gPingArguments.SetWaitingForEchoResp(false);
255     gPingArguments.IncrementEchoRespCount();
256
257     streamer_printf(sout, "Echo Response: %" PRIu64 "/%" PRIu64 "(%.2f%%) len=%u time=%.3fms\n", gPingArguments.GetEchoRespCount(),
258                     gPingArguments.GetEchoCount(),
259                     static_cast<double>(gPingArguments.GetEchoRespCount()) * 100 / gPingArguments.GetEchoCount(),
260                     payload->DataLength(), static_cast<double>(transitTime) / 1000);
261 }
262
263 void StartPinging(streamer_t * stream, char * destination)
264 {
265     CHIP_ERROR err = CHIP_NO_ERROR;
266
267     Transport::AdminPairingTable admins;
268     Transport::AdminPairingInfo * adminInfo = nullptr;
269     uint32_t maxEchoCount                   = 0;
270
271     if (!Inet::IPAddress::FromString(destination, gDestAddr))
272     {
273         streamer_printf(stream, "Invalid Echo Server IP address: %s\n", destination);
274         ExitNow(err = CHIP_ERROR_INVALID_ARGUMENT);
275     }
276
277     adminInfo = admins.AssignAdminId(gAdminId, kTestControllerNodeId);
278     VerifyOrExit(adminInfo != nullptr, err = CHIP_ERROR_NO_MEMORY);
279
280 #if INET_CONFIG_ENABLE_TCP_ENDPOINT
281     err = gTCPManager.Init(Transport::TcpListenParameters(&DeviceLayer::InetLayer)
282                                .SetAddressType(gDestAddr.Type())
283                                .SetListenPort(gPingArguments.GetEchoPort() + 1));
284     VerifyOrExit(err == CHIP_NO_ERROR, streamer_printf(stream, "Failed to init TCP manager error: %s\n", ErrorStr(err)));
285 #endif
286
287     err = gUDPManager.Init(Transport::UdpListenParameters(&DeviceLayer::InetLayer)
288                                .SetAddressType(gDestAddr.Type())
289                                .SetListenPort(gPingArguments.GetEchoPort() + 1));
290     VerifyOrExit(err == CHIP_NO_ERROR, streamer_printf(stream, "Failed to init UDP manager error: %s\n", ErrorStr(err)));
291
292 #if INET_CONFIG_ENABLE_TCP_ENDPOINT
293     if (gPingArguments.IsUsingTCP())
294     {
295         err = gSessionManager.Init(kTestControllerNodeId, &DeviceLayer::SystemLayer, &gTCPManager, &admins);
296         SuccessOrExit(err);
297
298         err = gExchangeManager.Init(&gSessionManager);
299         SuccessOrExit(err);
300     }
301     else
302 #endif
303     {
304         err = gSessionManager.Init(kTestControllerNodeId, &DeviceLayer::SystemLayer, &gUDPManager, &admins);
305         SuccessOrExit(err);
306
307         err = gExchangeManager.Init(&gSessionManager);
308         SuccessOrExit(err);
309     }
310
311     // Start the CHIP connection to the CHIP echo responder.
312     err = EstablishSecureSession(stream);
313     SuccessOrExit(err);
314
315     // TODO: temprary create a SecureSessionHandle from node id to unblock end-to-end test. Complete solution is tracked in PR:4451
316     err = gEchoClient.Init(&gExchangeManager, { kTestDeviceNodeId, 0, gAdminId });
317     SuccessOrExit(err);
318
319     // Arrange to get a callback whenever an Echo Response is received.
320     gEchoClient.SetEchoResponseReceived(HandleEchoResponseReceived);
321
322     maxEchoCount = gPingArguments.GetMaxEchoCount();
323
324     // Connection has been established. Now send the EchoRequests.
325     for (unsigned int i = 0; i < maxEchoCount; i++)
326     {
327         err = SendEchoRequest(stream);
328
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             // TODO:#5496: Use condition_varible to suspend the current thread and wake it up when response arrive.
339             sleep(1);
340         }
341
342         // Check if expected response was received.
343         if (gPingArguments.IsWaitingForEchoResp())
344         {
345             streamer_printf(stream, "No response received\n");
346             gPingArguments.SetWaitingForEchoResp(false);
347         }
348     }
349
350     gEchoClient.Shutdown();
351     gExchangeManager.Shutdown();
352     gSessionManager.Shutdown();
353
354 exit:
355     if ((err != CHIP_NO_ERROR))
356     {
357         streamer_printf(stream, "Ping failed with error: %s\n", ErrorStr(err));
358     }
359 }
360
361 void PrintUsage(streamer_t * stream)
362 {
363     streamer_printf(stream, "Usage: ping [options] <destination>\n\nOptions:\n");
364
365     // Need to split the help info to prevent overflowing the streamer_printf
366     // buffer (CONSOLE_DEFAULT_MAX_LINE 256)
367     streamer_printf(stream, "  -h              print help information\n");
368 #if INET_CONFIG_ENABLE_TCP_ENDPOINT
369     streamer_printf(stream, "  -u              use UDP (default)\n");
370     streamer_printf(stream, "  -t              use TCP\n");
371 #endif
372     streamer_printf(stream, "  -p  <port>      echo server port\n");
373     streamer_printf(stream, "  -i  <interval>  ping interval time in seconds\n");
374     streamer_printf(stream, "  -c  <count>     stop after <count> replies\n");
375     streamer_printf(stream, "  -r  <1|0>       enable or disable CRMP\n");
376     streamer_printf(stream, "  -s  <size>      payload size in bytes\n");
377 }
378
379 int cmd_ping(int argc, char ** argv)
380 {
381     streamer_t * sout = streamer_get();
382     int ret           = 0;
383     int optIndex      = 0;
384
385     gPingArguments.Reset();
386
387     while (optIndex < argc && argv[optIndex][0] == '-')
388     {
389         switch (argv[optIndex][1])
390         {
391         case 'h':
392             PrintUsage(sout);
393             return 0;
394 #if INET_CONFIG_ENABLE_TCP_ENDPOINT
395         case 'u':
396             gPingArguments.SetUsingTCP(false);
397             break;
398         case 't':
399             gPingArguments.SetUsingTCP(true);
400             break;
401 #endif
402         case 'i':
403             if (++optIndex >= argc || argv[optIndex][0] == '-')
404             {
405                 streamer_printf(sout, "Invalid argument specified for -i\n");
406                 return -1;
407             }
408             else
409             {
410                 gPingArguments.SetEchoInterval(atol(argv[optIndex]) * 1000);
411             }
412             break;
413         case 'c':
414             if (++optIndex >= argc || argv[optIndex][0] == '-')
415             {
416                 streamer_printf(sout, "Invalid argument specified for -c\n");
417                 return -1;
418             }
419             else
420             {
421                 gPingArguments.SetMaxEchoCount(atol(argv[optIndex]));
422             }
423             break;
424         case 'p':
425             if (++optIndex >= argc || argv[optIndex][0] == '-')
426             {
427                 streamer_printf(sout, "Invalid argument specified for -c\n");
428                 return -1;
429             }
430             else
431             {
432                 gPingArguments.SetEchoPort(atol(argv[optIndex]));
433             }
434             break;
435         case 's':
436             if (++optIndex >= argc || argv[optIndex][0] == '-')
437             {
438                 streamer_printf(sout, "Invalid argument specified for -s\n");
439                 return -1;
440             }
441             else
442             {
443                 gPingArguments.SetEchoReqSize(atol(argv[optIndex]));
444             }
445             break;
446         case 'r':
447             if (++optIndex >= argc || argv[optIndex][0] == '-')
448             {
449                 streamer_printf(sout, "Invalid argument specified for -r\n");
450                 return -1;
451             }
452             else
453             {
454                 int arg = atoi(argv[optIndex]);
455
456                 if (arg == 0)
457                 {
458                     gPingArguments.SetUsingCRMP(false);
459                 }
460                 else if (arg == 1)
461                 {
462                     gPingArguments.SetUsingCRMP(true);
463                 }
464                 else
465                 {
466                     ret = -1;
467                 }
468             }
469             break;
470         default:
471             ret = -1;
472         }
473
474         optIndex++;
475     }
476
477     if (optIndex >= argc)
478     {
479         streamer_printf(sout, "Missing IP address\n");
480         ret = -1;
481     }
482
483     if (ret == 0)
484     {
485         streamer_printf(sout, "IP address: %s\n", argv[optIndex]);
486         StartPinging(sout, argv[optIndex]);
487     }
488
489     return ret;
490 }
491
492 } // namespace
493
494 static shell_command_t cmds_ping[] = {
495     { &cmd_ping, "ping", "Using Echo Protocol to measure packet loss across network paths" },
496 };
497
498 void cmd_ping_init()
499 {
500     shell_register(cmds_ping, ArraySize(cmds_ping));
501 }