3 * Copyright (c) 2021 Project CHIP Authors
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
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>
35 #include <ChipShellCollection.h>
38 using namespace Shell;
39 using namespace Logging;
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;
58 mWaitingForEchoResp = false;
61 mEchoPort = CHIP_PORT;
64 uint64_t GetLastEchoTime() const { return mLastEchoTime; }
65 void SetLastEchoTime(uint64_t value) { mLastEchoTime = value; }
67 uint64_t GetEchoCount() const { return mEchoCount; }
68 void SetEchoCount(uint64_t value) { mEchoCount = value; }
69 void IncrementEchoCount() { mEchoCount++; }
71 uint64_t GetEchoRespCount() const { return mEchoRespCount; }
72 void SetEchoRespCount(uint64_t value) { mEchoRespCount = value; }
73 void IncrementEchoRespCount() { mEchoRespCount++; }
75 uint32_t GetMaxEchoCount() const { return mMaxEchoCount; }
76 void SetMaxEchoCount(uint32_t id) { mMaxEchoCount = id; }
78 uint32_t GetEchoInterval() const { return mEchoInterval; }
79 void SetEchoInterval(uint32_t value) { mEchoInterval = value; }
81 uint16_t GetEchoPort() const { return mEchoPort; }
82 void SetEchoPort(uint16_t value) { mEchoPort = value; }
84 bool IsWaitingForEchoResp() const { return mWaitingForEchoResp; }
85 void SetWaitingForEchoResp(bool value) { mWaitingForEchoResp = value; }
87 bool IsUsingTCP() const { return mUsingTCP; }
88 void SetUsingTCP(bool value) { mUsingTCP = value; }
90 bool IsUsingCRMP() const { return mUsingCRMP; }
91 void SetUsingCRMP(bool value) { mUsingCRMP = value; }
94 // The last time a echo request was attempted to be sent.
95 uint64_t mLastEchoTime;
97 // Count of the number of echo requests sent.
100 // Count of the number of echo responses received.
101 uint64_t mEchoRespCount;
103 // Max value for the number of echo requests sent.
104 uint32_t mMaxEchoCount;
106 // The CHIP Echo interval time in milliseconds.
107 uint32_t mEchoInterval;
111 // True, if the echo client is waiting for an echo response
112 // after sending an echo request, false otherwise.
113 bool mWaitingForEchoResp;
119 constexpr Transport::AdminId gAdminId = 0;
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;
128 bool EchoIntervalExpired(void)
130 uint64_t now = System::Timer::GetCurrentEpoch();
132 return (now >= gPingArguments.GetLastEchoTime() + gPingArguments.GetEchoInterval());
135 CHIP_ERROR SendEchoRequest(streamer_t * stream)
137 CHIP_ERROR err = CHIP_NO_ERROR;
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));
145 if (gPingArguments.IsUsingCRMP())
147 sendFlags.Set(Messaging::SendMessageFlags::kNone);
151 sendFlags.Set(Messaging::SendMessageFlags::kNoAutoRequestAck);
154 if (payloadBuf.IsNull())
156 streamer_printf(stream, "Unable to allocate packet buffer\n");
157 return CHIP_ERROR_NO_MEMORY;
160 gPingArguments.SetLastEchoTime(System::Timer::GetCurrentEpoch());
162 streamer_printf(stream, "\nSend echo request message to Node: %" PRIu64 "\n", kTestDeviceNodeId);
164 err = gEchoClient.SendEchoRequest(std::move(payloadBuf), sendFlags);
166 if (err == CHIP_NO_ERROR)
168 gPingArguments.SetWaitingForEchoResp(true);
169 gPingArguments.IncrementEchoCount();
173 streamer_printf(stream, "Send echo request failed, err: %s\n", ErrorStr(err));
179 CHIP_ERROR EstablishSecureSession(streamer_t * stream)
181 CHIP_ERROR err = CHIP_NO_ERROR;
183 Optional<Transport::PeerAddress> peerAddr;
184 SecurePairingUsingTestSecret * testSecurePairingSecret = chip::Platform::New<SecurePairingUsingTestSecret>();
185 VerifyOrExit(testSecurePairingSecret != nullptr, err = CHIP_ERROR_NO_MEMORY);
187 if (gPingArguments.IsUsingTCP())
189 peerAddr = Optional<Transport::PeerAddress>::Value(Transport::PeerAddress::TCP(gDestAddr, gPingArguments.GetEchoPort()));
193 peerAddr = Optional<Transport::PeerAddress>::Value(
194 Transport::PeerAddress::UDP(gDestAddr, gPingArguments.GetEchoPort(), INET_NULL_INTERFACEID));
197 // Attempt to connect to the peer.
198 err = gSessionManager.NewPairing(peerAddr, kTestDeviceNodeId, testSecurePairingSecret,
199 SecureSessionMgr::PairingDirection::kInitiator, gAdminId);
202 if (err != CHIP_NO_ERROR)
204 streamer_printf(stream, "Establish secure session failed, err: %s\n", ErrorStr(err));
205 gPingArguments.SetLastEchoTime(System::Timer::GetCurrentEpoch());
209 streamer_printf(stream, "Establish secure session succeeded\n");
215 void HandleEchoResponseReceived(Messaging::ExchangeContext * ec, System::PacketBufferHandle payload)
217 uint32_t respTime = System::Timer::GetCurrentEpoch();
218 uint32_t transitTime = respTime - gPingArguments.GetLastEchoTime();
219 streamer_t * sout = streamer_get();
221 gPingArguments.SetWaitingForEchoResp(false);
222 gPingArguments.IncrementEchoRespCount();
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);
230 void DriveIO(streamer_t * stream)
232 struct timeval sleepTime;
233 fd_set readFDs, writeFDs, exceptFDs;
237 sleepTime.tv_sec = 0;
238 sleepTime.tv_usec = kNetworkSleepTimeMsecs;
244 if (chip::DeviceLayer::SystemLayer.State() == chip::System::kLayerState_Initialized)
245 chip::DeviceLayer::SystemLayer.PrepareSelect(numFDs, &readFDs, &writeFDs, &exceptFDs, sleepTime);
247 if (chip::DeviceLayer::InetLayer.State == chip::Inet::InetLayer::kState_Initialized)
248 chip::DeviceLayer::InetLayer.PrepareSelect(numFDs, &readFDs, &writeFDs, &exceptFDs, sleepTime);
250 selectRes = select(numFDs, &readFDs, &writeFDs, &exceptFDs, &sleepTime);
253 streamer_printf(stream, "Select failed: %s\n", chip::ErrorStr(chip::System::MapErrorPOSIX(errno)));
257 if (chip::DeviceLayer::SystemLayer.State() == chip::System::kLayerState_Initialized)
259 chip::DeviceLayer::SystemLayer.HandleSelectResult(selectRes, &readFDs, &writeFDs, &exceptFDs);
262 if (chip::DeviceLayer::InetLayer.State == chip::Inet::InetLayer::kState_Initialized)
264 chip::DeviceLayer::InetLayer.HandleSelectResult(selectRes, &readFDs, &writeFDs, &exceptFDs);
268 void StartPinging(streamer_t * stream, char * destination)
270 CHIP_ERROR err = CHIP_NO_ERROR;
272 Transport::AdminPairingTable admins;
273 Transport::AdminPairingInfo * adminInfo = nullptr;
274 uint32_t maxEchoCount = 0;
276 if (!Inet::IPAddress::FromString(destination, gDestAddr))
278 streamer_printf(stream, "Invalid Echo Server IP address: %s\n", destination);
279 ExitNow(err = CHIP_ERROR_INVALID_ARGUMENT);
282 adminInfo = admins.AssignAdminId(gAdminId, kTestControllerNodeId);
283 VerifyOrExit(adminInfo != nullptr, err = CHIP_ERROR_NO_MEMORY);
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)));
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)));
295 if (gPingArguments.IsUsingTCP())
297 err = gSessionManager.Init(kTestControllerNodeId, &DeviceLayer::SystemLayer, &gTCPManager, &admins);
300 err = gExchangeManager.Init(kTestControllerNodeId, &gTCPManager, &gSessionManager);
305 err = gSessionManager.Init(kTestControllerNodeId, &DeviceLayer::SystemLayer, &gUDPManager, &admins);
308 err = gExchangeManager.Init(kTestControllerNodeId, &gUDPManager, &gSessionManager);
312 // Start the CHIP connection to the CHIP echo responder.
313 err = EstablishSecureSession(stream);
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 });
320 // Arrange to get a callback whenever an Echo Response is received.
321 gEchoClient.SetEchoResponseReceived(HandleEchoResponseReceived);
323 maxEchoCount = gPingArguments.GetMaxEchoCount();
325 // Connection has been established. Now send the EchoRequests.
326 for (unsigned int i = 0; i < maxEchoCount; i++)
328 err = SendEchoRequest(stream);
329 if (err != CHIP_NO_ERROR)
331 streamer_printf(stream, "Send request failed: %s\n", ErrorStr(err));
335 // Wait for response until the Echo interval.
336 while (!EchoIntervalExpired())
341 // Check if expected response was received.
342 if (gPingArguments.IsWaitingForEchoResp())
344 streamer_printf(stream, "No response received\n");
345 gPingArguments.SetWaitingForEchoResp(false);
349 gEchoClient.Shutdown();
350 gExchangeManager.Shutdown();
351 gSessionManager.Shutdown();
354 if ((err != CHIP_NO_ERROR))
356 streamer_printf(stream, "Ping failed with error: %s\n", ErrorStr(err));
360 void PrintUsage(streamer_t * stream)
362 streamer_printf(stream, "Usage: ping [options] <destination>\n\nOptions:\n");
364 streamer_printf(stream,
365 " -h print help information\n"
366 " -u use UDP (default)\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");
374 int cmd_ping(int argc, char ** argv)
376 streamer_t * sout = streamer_get();
380 gPingArguments.Reset();
382 while (optIndex < argc && argv[optIndex][0] == '-')
384 switch (argv[optIndex][1])
390 gPingArguments.SetUsingTCP(false);
393 gPingArguments.SetUsingTCP(true);
396 if (++optIndex >= argc || argv[optIndex][0] == '-')
398 streamer_printf(sout, "Invalid argument specified for -i\n");
403 gPingArguments.SetEchoInterval(atol(argv[optIndex]) * 1000);
407 if (++optIndex >= argc || argv[optIndex][0] == '-')
409 streamer_printf(sout, "Invalid argument specified for -c\n");
414 gPingArguments.SetMaxEchoCount(atol(argv[optIndex]));
418 if (++optIndex >= argc || argv[optIndex][0] == '-')
420 streamer_printf(sout, "Invalid argument specified for -c\n");
425 gPingArguments.SetEchoPort(atol(argv[optIndex]));
429 if (++optIndex >= argc || argv[optIndex][0] == '-')
431 streamer_printf(sout, "Invalid argument specified for -r\n");
436 int arg = atoi(argv[optIndex]);
440 gPingArguments.SetUsingCRMP(false);
444 gPingArguments.SetUsingCRMP(true);
459 if (optIndex >= argc)
461 streamer_printf(sout, "Missing IP address\n");
467 streamer_printf(sout, "IP address: %s\n", argv[optIndex]);
468 StartPinging(sout, argv[optIndex]);
476 static shell_command_t cmds_ping[] = {
477 { &cmd_ping, "ping", "Using Echo Protocol to measure packet loss across network paths" },
482 shell_register(cmds_ping, ArraySize(cmds_ping));