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 <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>
35 #include <ChipShellCollection.h>
38 using namespace Shell;
39 using namespace Logging;
41 #if INET_CONFIG_ENABLE_TCP_ENDPOINT
42 constexpr size_t kMaxTcpActiveConnectionCount = 4;
43 constexpr size_t kMaxTcpPendingPackets = 4;
45 constexpr size_t kMaxPayloadSize = 1280;
60 mWaitingForEchoResp = false;
61 #if INET_CONFIG_ENABLE_TCP_ENDPOINT
65 mEchoPort = CHIP_PORT;
68 uint64_t GetLastEchoTime() const { return mLastEchoTime; }
69 void SetLastEchoTime(uint64_t value) { mLastEchoTime = value; }
71 uint64_t GetEchoCount() const { return mEchoCount; }
72 void SetEchoCount(uint64_t value) { mEchoCount = value; }
73 void IncrementEchoCount() { mEchoCount++; }
75 uint64_t GetEchoRespCount() const { return mEchoRespCount; }
76 void SetEchoRespCount(uint64_t value) { mEchoRespCount = value; }
77 void IncrementEchoRespCount() { mEchoRespCount++; }
79 uint32_t GetMaxEchoCount() const { return mMaxEchoCount; }
80 void SetMaxEchoCount(uint32_t id) { mMaxEchoCount = id; }
82 uint32_t GetEchoInterval() const { return mEchoInterval; }
83 void SetEchoInterval(uint32_t value) { mEchoInterval = value; }
85 uint32_t GetEchoReqSize() const { return mEchoReqSize; }
86 void SetEchoReqSize(uint32_t value) { mEchoReqSize = value; }
88 uint16_t GetEchoPort() const { return mEchoPort; }
89 void SetEchoPort(uint16_t value) { mEchoPort = value; }
91 bool IsWaitingForEchoResp() const { return mWaitingForEchoResp; }
92 void SetWaitingForEchoResp(bool value) { mWaitingForEchoResp = value; }
94 #if INET_CONFIG_ENABLE_TCP_ENDPOINT
95 bool IsUsingTCP() const { return mUsingTCP; }
96 void SetUsingTCP(bool value) { mUsingTCP = value; }
99 bool IsUsingCRMP() const { return mUsingCRMP; }
100 void SetUsingCRMP(bool value) { mUsingCRMP = value; }
103 // The last time a echo request was attempted to be sent.
104 uint64_t mLastEchoTime;
106 // Count of the number of echo requests sent.
109 // Count of the number of echo responses received.
110 uint64_t mEchoRespCount;
112 // The CHIP Echo request payload size in bytes.
113 uint32_t mEchoReqSize;
115 // Max value for the number of echo requests sent.
116 uint32_t mMaxEchoCount;
118 // The CHIP Echo interval time in milliseconds.
119 uint32_t mEchoInterval;
123 // True, if the echo client is waiting for an echo response
124 // after sending an echo request, false otherwise.
125 bool mWaitingForEchoResp;
127 #if INET_CONFIG_ENABLE_TCP_ENDPOINT
134 constexpr Transport::AdminId gAdminId = 0;
136 Protocols::Echo::EchoClient gEchoClient;
138 TransportMgr<Transport::UDP> gUDPManager;
140 #if INET_CONFIG_ENABLE_TCP_ENDPOINT
141 TransportMgr<Transport::TCP<kMaxTcpActiveConnectionCount, kMaxTcpPendingPackets>> gTCPManager;
144 Messaging::ExchangeManager gExchangeManager;
145 SecureSessionMgr gSessionManager;
146 Inet::IPAddress gDestAddr;
148 bool EchoIntervalExpired(void)
150 uint64_t now = System::Timer::GetCurrentEpoch();
152 return (now >= gPingArguments.GetLastEchoTime() + gPingArguments.GetEchoInterval());
155 CHIP_ERROR SendEchoRequest(streamer_t * stream)
157 CHIP_ERROR err = CHIP_NO_ERROR;
159 Messaging::SendFlags sendFlags;
160 System::PacketBufferHandle payloadBuf;
161 char * requestData = nullptr;
163 uint32_t size = gPingArguments.GetEchoReqSize();
164 VerifyOrExit(size <= kMaxPayloadSize, err = CHIP_ERROR_INVALID_MESSAGE_LENGTH);
166 requestData = static_cast<char *>(chip::Platform::MemoryAlloc(size));
167 VerifyOrExit(requestData != nullptr, err = CHIP_ERROR_NO_MEMORY);
169 snprintf(requestData, size, "Echo Message %" PRIu64 "\n", gPingArguments.GetEchoCount());
171 payloadBuf = MessagePacketBuffer::NewWithData(requestData, size);
172 VerifyOrExit(!payloadBuf.IsNull(), err = CHIP_ERROR_NO_MEMORY);
174 if (gPingArguments.IsUsingCRMP())
176 sendFlags.Set(Messaging::SendMessageFlags::kNone);
180 sendFlags.Set(Messaging::SendMessageFlags::kNoAutoRequestAck);
183 gPingArguments.SetLastEchoTime(System::Timer::GetCurrentEpoch());
185 streamer_printf(stream, "\nSend echo request message with payload size: %d bytes to Node: %" PRIu64 "\n", size,
188 err = gEchoClient.SendEchoRequest(std::move(payloadBuf), sendFlags);
190 if (err == CHIP_NO_ERROR)
192 gPingArguments.SetWaitingForEchoResp(true);
193 gPingArguments.IncrementEchoCount();
197 if (requestData != nullptr)
199 chip::Platform::MemoryFree(requestData);
202 if (err != CHIP_NO_ERROR)
204 streamer_printf(stream, "Send echo request failed, err: %s\n", ErrorStr(err));
210 CHIP_ERROR EstablishSecureSession(streamer_t * stream)
212 CHIP_ERROR err = CHIP_NO_ERROR;
214 Optional<Transport::PeerAddress> peerAddr;
215 SecurePairingUsingTestSecret * testSecurePairingSecret = chip::Platform::New<SecurePairingUsingTestSecret>();
216 VerifyOrExit(testSecurePairingSecret != nullptr, err = CHIP_ERROR_NO_MEMORY);
218 #if INET_CONFIG_ENABLE_TCP_ENDPOINT
219 if (gPingArguments.IsUsingTCP())
221 peerAddr = Optional<Transport::PeerAddress>::Value(Transport::PeerAddress::TCP(gDestAddr, gPingArguments.GetEchoPort()));
226 peerAddr = Optional<Transport::PeerAddress>::Value(
227 Transport::PeerAddress::UDP(gDestAddr, gPingArguments.GetEchoPort(), INET_NULL_INTERFACEID));
230 // Attempt to connect to the peer.
231 err = gSessionManager.NewPairing(peerAddr, kTestDeviceNodeId, testSecurePairingSecret,
232 SecureSessionMgr::PairingDirection::kInitiator, gAdminId);
235 if (err != CHIP_NO_ERROR)
237 streamer_printf(stream, "Establish secure session failed, err: %s\n", ErrorStr(err));
238 gPingArguments.SetLastEchoTime(System::Timer::GetCurrentEpoch());
242 streamer_printf(stream, "Establish secure session succeeded\n");
248 void HandleEchoResponseReceived(Messaging::ExchangeContext * ec, System::PacketBufferHandle payload)
250 uint32_t respTime = System::Timer::GetCurrentEpoch();
251 uint32_t transitTime = respTime - gPingArguments.GetLastEchoTime();
252 streamer_t * sout = streamer_get();
254 gPingArguments.SetWaitingForEchoResp(false);
255 gPingArguments.IncrementEchoRespCount();
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);
263 void StartPinging(streamer_t * stream, char * destination)
265 CHIP_ERROR err = CHIP_NO_ERROR;
267 Transport::AdminPairingTable admins;
268 Transport::AdminPairingInfo * adminInfo = nullptr;
269 uint32_t maxEchoCount = 0;
271 if (!Inet::IPAddress::FromString(destination, gDestAddr))
273 streamer_printf(stream, "Invalid Echo Server IP address: %s\n", destination);
274 ExitNow(err = CHIP_ERROR_INVALID_ARGUMENT);
277 adminInfo = admins.AssignAdminId(gAdminId, kTestControllerNodeId);
278 VerifyOrExit(adminInfo != nullptr, err = CHIP_ERROR_NO_MEMORY);
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)));
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)));
292 #if INET_CONFIG_ENABLE_TCP_ENDPOINT
293 if (gPingArguments.IsUsingTCP())
295 err = gSessionManager.Init(kTestControllerNodeId, &DeviceLayer::SystemLayer, &gTCPManager, &admins);
298 err = gExchangeManager.Init(&gSessionManager);
304 err = gSessionManager.Init(kTestControllerNodeId, &DeviceLayer::SystemLayer, &gUDPManager, &admins);
307 err = gExchangeManager.Init(&gSessionManager);
311 // Start the CHIP connection to the CHIP echo responder.
312 err = EstablishSecureSession(stream);
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 });
319 // Arrange to get a callback whenever an Echo Response is received.
320 gEchoClient.SetEchoResponseReceived(HandleEchoResponseReceived);
322 maxEchoCount = gPingArguments.GetMaxEchoCount();
324 // Connection has been established. Now send the EchoRequests.
325 for (unsigned int i = 0; i < maxEchoCount; i++)
327 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())
338 // TODO:#5496: Use condition_varible to suspend the current thread and wake it up when response arrive.
342 // Check if expected response was received.
343 if (gPingArguments.IsWaitingForEchoResp())
345 streamer_printf(stream, "No response received\n");
346 gPingArguments.SetWaitingForEchoResp(false);
350 gEchoClient.Shutdown();
351 gExchangeManager.Shutdown();
352 gSessionManager.Shutdown();
355 if ((err != CHIP_NO_ERROR))
357 streamer_printf(stream, "Ping failed with error: %s\n", ErrorStr(err));
361 void PrintUsage(streamer_t * stream)
363 streamer_printf(stream, "Usage: ping [options] <destination>\n\nOptions:\n");
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");
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");
379 int cmd_ping(int argc, char ** argv)
381 streamer_t * sout = streamer_get();
385 gPingArguments.Reset();
387 while (optIndex < argc && argv[optIndex][0] == '-')
389 switch (argv[optIndex][1])
394 #if INET_CONFIG_ENABLE_TCP_ENDPOINT
396 gPingArguments.SetUsingTCP(false);
399 gPingArguments.SetUsingTCP(true);
403 if (++optIndex >= argc || argv[optIndex][0] == '-')
405 streamer_printf(sout, "Invalid argument specified for -i\n");
410 gPingArguments.SetEchoInterval(atol(argv[optIndex]) * 1000);
414 if (++optIndex >= argc || argv[optIndex][0] == '-')
416 streamer_printf(sout, "Invalid argument specified for -c\n");
421 gPingArguments.SetMaxEchoCount(atol(argv[optIndex]));
425 if (++optIndex >= argc || argv[optIndex][0] == '-')
427 streamer_printf(sout, "Invalid argument specified for -c\n");
432 gPingArguments.SetEchoPort(atol(argv[optIndex]));
436 if (++optIndex >= argc || argv[optIndex][0] == '-')
438 streamer_printf(sout, "Invalid argument specified for -s\n");
443 gPingArguments.SetEchoReqSize(atol(argv[optIndex]));
447 if (++optIndex >= argc || argv[optIndex][0] == '-')
449 streamer_printf(sout, "Invalid argument specified for -r\n");
454 int arg = atoi(argv[optIndex]);
458 gPingArguments.SetUsingCRMP(false);
462 gPingArguments.SetUsingCRMP(true);
477 if (optIndex >= argc)
479 streamer_printf(sout, "Missing IP address\n");
485 streamer_printf(sout, "IP address: %s\n", argv[optIndex]);
486 StartPinging(sout, argv[optIndex]);
494 static shell_command_t cmds_ping[] = {
495 { &cmd_ping, "ping", "Using Echo Protocol to measure packet loss across network paths" },
500 shell_register(cmds_ping, ArraySize(cmds_ping));