From 6c03a1e962e61c4fa20746b3433ef02b588d469b Mon Sep 17 00:00:00 2001 From: Sam Kumar Date: Mon, 19 Jul 2021 14:32:49 -0700 Subject: [PATCH] [cli] add support for TCP in the CLI tool (#6770) --- Android.mk | 1 + include/openthread/instance.h | 2 +- include/openthread/tcp.h | 33 ++- src/cli/BUILD.gn | 2 + src/cli/CMakeLists.txt | 1 + src/cli/Makefile.am | 2 + src/cli/cli.cpp | 10 + src/cli/cli.hpp | 15 + src/cli/cli_config.h | 31 ++ src/cli/cli_tcp.cpp | 520 ++++++++++++++++++++++++++++++++++ src/cli/cli_tcp.hpp | 159 +++++++++++ 11 files changed, 770 insertions(+), 6 deletions(-) create mode 100644 src/cli/cli_tcp.cpp create mode 100644 src/cli/cli_tcp.hpp diff --git a/Android.mk b/Android.mk index eb2a8b7d9..03b3a837a 100644 --- a/Android.mk +++ b/Android.mk @@ -500,6 +500,7 @@ LOCAL_SRC_FILES := \ src/cli/cli_network_data.cpp \ src/cli/cli_srp_client.cpp \ src/cli/cli_srp_server.cpp \ + src/cli/cli_tcp.cpp \ src/cli/cli_udp.cpp \ $(NULL) diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 3f8b812e5..ad83f50c1 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (138) +#define OPENTHREAD_API_VERSION (139) /** * @addtogroup api-instance diff --git a/include/openthread/tcp.h b/include/openthread/tcp.h index 08e926991..9d215b9b1 100644 --- a/include/openthread/tcp.h +++ b/include/openthread/tcp.h @@ -197,7 +197,7 @@ typedef void (*otTcpDisconnected)(otTcpEndpoint *aEndpoint, otTcpDisconnectedRea * should only interact with it via the TCP API functions whose signatures are * provided in this file. */ -typedef struct otTcpEndpoint +struct otTcpEndpoint { struct otTcpEndpoint *mNext; ///< A pointer to the next TCP endpoint (internal use only) otInstance * mInstance; ///< A pointer to the OpenThread instance associated with this TCP endpoint @@ -210,7 +210,7 @@ typedef struct otTcpEndpoint otTcpDisconnected mDisconnectedCallback; ///< "Disconnected" callback function /* Other implementation-defined fields go here. */ -} otTcpEndpoint; +}; /** * This structure contains arguments to the otTcpEndpointInitialize() function. @@ -230,13 +230,36 @@ typedef struct otTcpEndpointInitializeArgs size_t mReceiveBufferSize; ///< Size of memory provided to the system for the TCP receive buffer } otTcpEndpointInitializeArgs; +/** + * @def OT_TCP_RECEIVE_BUFFER_SIZE_FEW_HOPS + * + * Recommended buffer size for TCP connections that traverse about 3 wireless + * hops or fewer. + * + * On platforms where memory is particularly constrained and in situations + * where high bandwidth is not necessary, it may be desirable to manually + * select a smaller buffer size. + */ +#define OT_TCP_RECEIVE_BUFFER_SIZE_FEW_HOPS 2599 + +/** + * @def OT_TCP_RECEIVE_BUFFER_SIZE_MANY_HOPS + * + * Recommended buffer size for TCP connections that traverse many wireless + * hops. + * + * If the TCP connection traverses a very large number of hops (more than 6 or + * so), then it may be advisable to select a large buffer size manually. + */ +#define OT_TCP_RECEIVE_BUFFER_SIZE_MANY_HOPS 4158 + /** * Initializes a TCP endpoint. * * Calling this function causes OpenThread to keep track of the TCP endpoint * and store and retrieve TCP data inside the @p aEndpoint. The application * should refrain from directly accessing or modifying the fields in - * @p aEndpoint. If the application needs to reclaimthe memory backing + * @p aEndpoint. If the application needs to reclaim the memory backing * @p aEndpoint, it should call otTcpEndpointDeinitialize(). * * @param[in] aInstance A pointer to an OpenThread instance. @@ -548,7 +571,7 @@ typedef void (*otTcpAcceptDone)(otTcpListener *aListener, otTcpEndpoint *aEndpoi * should only interact with it via the TCP API functions whose signatures are * provided in this file. */ -typedef struct otTcpListener +struct otTcpListener { struct otTcpListener *mNext; ///< A pointer to the next TCP listener (internal use only) otInstance * mInstance; ///< A pointer to the OpenThread instance associated with this TCP listener @@ -558,7 +581,7 @@ typedef struct otTcpListener otTcpAcceptDone mAcceptDoneCallback; ///< "Accept done" callback function /* Other implementation-defined fields go here. */ -} otTcpListener; +}; /** * This structure contains arguments to the otTcpListenerInitialize() function. diff --git a/src/cli/BUILD.gn b/src/cli/BUILD.gn index cce57689d..ce2314c1c 100644 --- a/src/cli/BUILD.gn +++ b/src/cli/BUILD.gn @@ -47,6 +47,8 @@ openthread_cli_sources = [ "cli_srp_client.hpp", "cli_srp_server.cpp", "cli_srp_server.hpp", + "cli_tcp.cpp", + "cli_tcp.hpp", "cli_udp.cpp", "cli_udp.hpp", "x509_cert_key.hpp", diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index c60eb051e..77a01ce8b 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -41,6 +41,7 @@ set(COMMON_SOURCES cli_network_data.cpp cli_srp_client.cpp cli_srp_server.cpp + cli_tcp.cpp cli_udp.cpp ) diff --git a/src/cli/Makefile.am b/src/cli/Makefile.am index 060ff156f..3fdc14e23 100644 --- a/src/cli/Makefile.am +++ b/src/cli/Makefile.am @@ -149,6 +149,7 @@ SOURCES_COMMON = \ cli_network_data.cpp \ cli_srp_client.cpp \ cli_srp_server.cpp \ + cli_tcp.cpp \ cli_udp.cpp \ $(NULL) @@ -171,6 +172,7 @@ noinst_HEADERS = \ cli_network_data.hpp \ cli_srp_client.hpp \ cli_srp_server.hpp \ + cli_tcp.hpp \ cli_udp.hpp \ x509_cert_key.hpp \ $(NULL) diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index dd0b79efb..03f9b3d5a 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -106,6 +106,9 @@ Interpreter::Interpreter(Instance *aInstance, otCliOutputCallback aCallback, voi , mDataset(*this) , mNetworkData(*this) , mUdp(*this) +#if OPENTHREAD_CONFIG_TCP_ENABLE && OPENTHREAD_CONFIG_CLI_TCP_ENABLE + , mTcp(*this) +#endif #if OPENTHREAD_CONFIG_COAP_API_ENABLE , mCoap(*this) #endif @@ -4084,6 +4087,13 @@ exit: return error; } +#if OPENTHREAD_CONFIG_TCP_ENABLE && OPENTHREAD_CONFIG_CLI_TCP_ENABLE +otError Interpreter::ProcessTcp(Arg aArgs[]) +{ + return mTcp.Process(aArgs); +} +#endif + otError Interpreter::ProcessUdp(Arg aArgs[]) { return mUdp.Process(aArgs); diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index 2ae46ae5a..9042dc122 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -47,6 +47,9 @@ #include #include #include +#if OPENTHREAD_CONFIG_TCP_ENABLE && OPENTHREAD_CONFIG_CLI_TCP_ENABLE +#include +#endif #include #include #include @@ -57,6 +60,7 @@ #include "cli/cli_network_data.hpp" #include "cli/cli_srp_client.hpp" #include "cli/cli_srp_server.hpp" +#include "cli/cli_tcp.hpp" #include "cli/cli_udp.hpp" #if OPENTHREAD_CONFIG_COAP_API_ENABLE #include "cli/cli_coap.hpp" @@ -97,6 +101,7 @@ class Interpreter friend class NetworkData; friend class SrpClient; friend class SrpServer; + friend class TcpExample; friend class UdpExample; public: @@ -610,6 +615,9 @@ private: otError ProcessThread(Arg aArgs[]); otError ProcessDataset(Arg aArgs[]); otError ProcessTxPower(Arg aArgs[]); +#if OPENTHREAD_CONFIG_TCP_ENABLE && OPENTHREAD_CONFIG_CLI_TCP_ENABLE + otError ProcessTcp(Arg aArgs[]); +#endif otError ProcessUdp(Arg aArgs[]); otError ProcessUnsecurePort(Arg aArgs[]); otError ProcessVersion(Arg aArgs[]); @@ -876,6 +884,9 @@ private: {"srp", &Interpreter::ProcessSrp}, #endif {"state", &Interpreter::ProcessState}, +#if OPENTHREAD_CONFIG_TCP_ENABLE && OPENTHREAD_CONFIG_CLI_TCP_ENABLE + {"tcp", &Interpreter::ProcessTcp}, +#endif {"thread", &Interpreter::ProcessThread}, {"txpower", &Interpreter::ProcessTxPower}, {"udp", &Interpreter::ProcessUdp}, @@ -899,6 +910,10 @@ private: NetworkData mNetworkData; UdpExample mUdp; +#if OPENTHREAD_CONFIG_TCP_ENABLE && OPENTHREAD_CONFIG_CLI_TCP_ENABLE + TcpExample mTcp; +#endif + #if OPENTHREAD_CONFIG_COAP_API_ENABLE Coap mCoap; #endif diff --git a/src/cli/cli_config.h b/src/cli/cli_config.h index 878b270dc..e8d6758bb 100644 --- a/src/cli/cli_config.h +++ b/src/cli/cli_config.h @@ -37,6 +37,8 @@ #include "openthread-core-config.h" +#include + #ifndef OPENTHREAD_POSIX #if defined(__ANDROID__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__linux__) || defined(__NetBSD__) || \ defined(__unix__) @@ -56,4 +58,33 @@ #define OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH 384 #endif +/** + * @def OPENTHREAD_CONFIG_CLI_TCP_ENABLE + * + * Indicates whether TCP should be enabled in the CLI tool. + * + */ +#ifndef OPENTHREAD_CONFIG_CLI_TCP_ENABLE +#define OPENTHREAD_CONFIG_CLI_TCP_ENABLE 1 +#endif + +/** + * @def OPENTHREAD_CONFIG_CLI_TCP_DEFAULT_BENCHMARK_SIZE + * + * The number of bytes to transfer for the TCP benchmark in the CLI. + * + */ +#ifndef OPENTHREAD_CONFIG_CLI_TCP_DEFAULT_BENCHMARK_SIZE +#define OPENTHREAD_CONFIG_CLI_TCP_DEFAULT_BENCHMARK_SIZE (72 << 10) +#endif + +/** + * @def OPENTHREAD_CONFIG_CLI_TCP_RECEIVE_BUFFER_SIZE + * + * The size of memory used for the TCP receive buffer, in bytes. + */ +#ifndef OPENTHREAD_CONFIG_CLI_TCP_RECEIVE_BUFFER_SIZE +#define OPENTHREAD_CONFIG_CLI_TCP_RECEIVE_BUFFER_SIZE OT_TCP_RECEIVE_BUFFER_SIZE_FEW_HOPS +#endif + #endif // CONFIG_CLI_H_ diff --git a/src/cli/cli_tcp.cpp b/src/cli/cli_tcp.cpp new file mode 100644 index 000000000..1c9d5963b --- /dev/null +++ b/src/cli/cli_tcp.cpp @@ -0,0 +1,520 @@ +/* + * Copyright (c) 2021, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file implements a TCP CLI tool. + */ + +#include "openthread-core-config.h" + +#include "cli_config.h" + +#if OPENTHREAD_CONFIG_TCP_ENABLE && OPENTHREAD_CONFIG_CLI_TCP_ENABLE + +#include "cli_tcp.hpp" + +#include + +#include "cli/cli.hpp" +#include "common/encoding.hpp" +#include "common/timer.hpp" + +namespace ot { +namespace Cli { + +constexpr TcpExample::Command TcpExample::sCommands[]; + +TcpExample::TcpExample(Interpreter &aInterpreter) + : mInterpreter(aInterpreter) + , mInitialized(false) + , mEndpointConnected(false) + , mSendBusy(false) + , mBenchmarkBytesTotal(0) + , mBenchmarkLinksLeft(0) +{ +} + +otError TcpExample::ProcessHelp(Arg aArgs[]) +{ + OT_UNUSED_VARIABLE(aArgs); + + for (const Command &command : sCommands) + { + mInterpreter.OutputLine(command.mName); + } + + return OT_ERROR_NONE; +} + +otError TcpExample::ProcessInit(Arg aArgs[]) +{ + otError error = OT_ERROR_NONE; + size_t receiveBufferSize; + + VerifyOrExit(!mInitialized, error = OT_ERROR_ALREADY); + + if (aArgs[0].IsEmpty()) + { + receiveBufferSize = sizeof(mReceiveBuffer); + } + else + { + uint32_t windowSize; + + SuccessOrExit(error = aArgs[0].ParseAsUint32(windowSize)); + VerifyOrExit(aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + + receiveBufferSize = windowSize + ((windowSize + 7) >> 3); + VerifyOrExit(receiveBufferSize <= sizeof(mReceiveBuffer) && receiveBufferSize != 0, + error = OT_ERROR_INVALID_ARGS); + } + + { + otTcpEndpointInitializeArgs endpointArgs; + + memset(&endpointArgs, 0x00, sizeof(endpointArgs)); + endpointArgs.mEstablishedCallback = HandleTcpEstablishedCallback; + endpointArgs.mSendDoneCallback = HandleTcpSendDoneCallback; + endpointArgs.mReceiveAvailableCallback = HandleTcpReceiveAvailableCallback; + endpointArgs.mDisconnectedCallback = HandleTcpDisconnectedCallback; + endpointArgs.mContext = this; + endpointArgs.mReceiveBuffer = mReceiveBuffer; + endpointArgs.mReceiveBufferSize = receiveBufferSize; + + SuccessOrExit(error = otTcpEndpointInitialize(mInterpreter.mInstance, &mEndpoint, &endpointArgs)); + } + + { + otTcpListenerInitializeArgs listenerArgs; + + memset(&listenerArgs, 0x00, sizeof(listenerArgs)); + listenerArgs.mAcceptReadyCallback = HandleTcpAcceptReadyCallback; + listenerArgs.mAcceptDoneCallback = HandleTcpAcceptDoneCallback; + listenerArgs.mContext = this; + + error = otTcpListenerInitialize(mInterpreter.mInstance, &mListener, &listenerArgs); + if (error != OT_ERROR_NONE) + { + IgnoreReturnValue(otTcpEndpointDeinitialize(&mEndpoint)); + ExitNow(); + } + } + + mInitialized = true; + +exit: + return error; +} + +otError TcpExample::ProcessDeinit(Arg aArgs[]) +{ + otError error = OT_ERROR_NONE; + otError endpointError; + otError listenerError; + + VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + VerifyOrExit(mInitialized, error = OT_ERROR_INVALID_STATE); + + endpointError = otTcpEndpointDeinitialize(&mEndpoint); + mSendBusy = false; + + listenerError = otTcpListenerDeinitialize(&mListener); + mInitialized = false; + + SuccessOrExit(error = endpointError); + SuccessOrExit(error = listenerError); + +exit: + return error; +} + +otError TcpExample::ProcessBind(Arg aArgs[]) +{ + otError error; + otSockAddr sockaddr; + + VerifyOrExit(mInitialized, error = OT_ERROR_INVALID_STATE); + + SuccessOrExit(error = aArgs[0].ParseAsIp6Address(sockaddr.mAddress)); + SuccessOrExit(error = aArgs[1].ParseAsUint16(sockaddr.mPort)); + VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + + error = otTcpBind(&mEndpoint, &sockaddr); + +exit: + return error; +} + +otError TcpExample::ProcessConnect(Arg aArgs[]) +{ + otError error; + otSockAddr sockaddr; + + VerifyOrExit(mInitialized, error = OT_ERROR_INVALID_STATE); + + SuccessOrExit(error = aArgs[0].ParseAsIp6Address(sockaddr.mAddress)); + SuccessOrExit(error = aArgs[1].ParseAsUint16(sockaddr.mPort)); + VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + + SuccessOrExit(error = otTcpConnect(&mEndpoint, &sockaddr, OT_TCP_CONNECT_NO_FAST_OPEN)); + mEndpointConnected = false; + +exit: + return error; +} + +otError TcpExample::ProcessSend(Arg aArgs[]) +{ + otError error; + + VerifyOrExit(mInitialized, error = OT_ERROR_INVALID_STATE); + VerifyOrExit(!mSendBusy, error = OT_ERROR_BUSY); + VerifyOrExit(mBenchmarkBytesTotal == 0, error = OT_ERROR_BUSY); + + mSendLink.mNext = nullptr; + mSendLink.mData = mSendBuffer; + VerifyOrExit(!aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + mSendLink.mLength = OT_MIN(aArgs[0].GetLength(), sizeof(mSendBuffer)); + memcpy(mSendBuffer, aArgs[0].GetCString(), mSendLink.mLength); + VerifyOrExit(aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + + SuccessOrExit(error = otTcpSendByReference(&mEndpoint, &mSendLink, 0)); + mSendBusy = true; + +exit: + return error; +} + +otError TcpExample::ProcessBenchmark(Arg aArgs[]) +{ + otError error; + uint32_t toSendOut; + + VerifyOrExit(!mSendBusy, error = OT_ERROR_BUSY); + VerifyOrExit(mBenchmarkBytesTotal == 0, error = OT_ERROR_BUSY); + + if (aArgs[0].IsEmpty()) + { + mBenchmarkBytesTotal = OPENTHREAD_CONFIG_CLI_TCP_DEFAULT_BENCHMARK_SIZE; + } + else + { + SuccessOrExit(error = aArgs[0].ParseAsUint32(mBenchmarkBytesTotal)); + VerifyOrExit(mBenchmarkBytesTotal != 0, error = OT_ERROR_INVALID_ARGS); + } + VerifyOrExit(aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + + memset(mSendBuffer, 'a', sizeof(mSendBuffer)); + + mBenchmarkLinksLeft = (mBenchmarkBytesTotal + sizeof(mSendBuffer) - 1) / sizeof(mSendBuffer); + toSendOut = OT_MIN(OT_ARRAY_LENGTH(mBenchmarkLinks), mBenchmarkLinksLeft); + mBenchmarkStart = TimerMilli::GetNow(); + for (uint32_t i = 0; i != toSendOut; i++) + { + mBenchmarkLinks[i].mNext = nullptr; + mBenchmarkLinks[i].mData = mSendBuffer; + mBenchmarkLinks[i].mLength = sizeof(mSendBuffer); + if (i == 0 && mBenchmarkBytesTotal % sizeof(mSendBuffer) != 0) + { + mBenchmarkLinks[i].mLength = mBenchmarkBytesTotal % sizeof(mSendBuffer); + } + SuccessOrExit(error = otTcpSendByReference(&mEndpoint, &mBenchmarkLinks[i], + i == toSendOut - 1 ? 0 : OT_TCP_SEND_MORE_TO_COME)); + } + +exit: + if (error != OT_ERROR_NONE) + { + mBenchmarkBytesTotal = 0; + mBenchmarkLinksLeft = 0; + } + return error; +} + +otError TcpExample::ProcessSendEnd(Arg aArgs[]) +{ + otError error; + + VerifyOrExit(mInitialized, error = OT_ERROR_INVALID_STATE); + VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + + error = otTcpSendEndOfStream(&mEndpoint); + +exit: + return error; +} + +otError TcpExample::ProcessAbort(Arg aArgs[]) +{ + otError error; + + VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + VerifyOrExit(mInitialized, error = OT_ERROR_INVALID_STATE); + + SuccessOrExit(error = otTcpAbort(&mEndpoint)); + mEndpointConnected = false; + +exit: + return error; +} + +otError TcpExample::ProcessListen(Arg aArgs[]) +{ + otError error; + otSockAddr sockaddr; + + VerifyOrExit(mInitialized, error = OT_ERROR_INVALID_STATE); + + SuccessOrExit(error = aArgs[0].ParseAsIp6Address(sockaddr.mAddress)); + SuccessOrExit(error = aArgs[1].ParseAsUint16(sockaddr.mPort)); + VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + + SuccessOrExit(error = otTcpStopListening(&mListener)); + error = otTcpListen(&mListener, &sockaddr); + +exit: + return error; +} + +otError TcpExample::ProcessStopListening(Arg aArgs[]) +{ + otError error; + + VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + VerifyOrExit(mInitialized, error = OT_ERROR_INVALID_STATE); + + error = otTcpStopListening(&mListener); + +exit: + return error; +} + +otError TcpExample::Process(Arg aArgs[]) +{ + otError error = OT_ERROR_INVALID_ARGS; + const Command *command; + + VerifyOrExit(!aArgs[0].IsEmpty(), IgnoreError(ProcessHelp(nullptr))); + + command = Utils::LookupTable::Find(aArgs[0].GetCString(), sCommands); + VerifyOrExit(command != nullptr, error = OT_ERROR_INVALID_COMMAND); + + error = (this->*command->mHandler)(aArgs + 1); + +exit: + return error; +} + +void TcpExample::HandleTcpEstablishedCallback(otTcpEndpoint *aEndpoint) +{ + static_cast(otTcpEndpointGetContext(aEndpoint))->HandleTcpEstablished(aEndpoint); +} + +void TcpExample::HandleTcpSendDoneCallback(otTcpEndpoint *aEndpoint, otLinkedBuffer *aData) +{ + static_cast(otTcpEndpointGetContext(aEndpoint))->HandleTcpSendDone(aEndpoint, aData); +} + +void TcpExample::HandleTcpReceiveAvailableCallback(otTcpEndpoint *aEndpoint, + size_t aBytesAvailable, + bool aEndOfStream, + size_t aBytesRemaining) +{ + static_cast(otTcpEndpointGetContext(aEndpoint)) + ->HandleTcpReceiveAvailable(aEndpoint, aBytesAvailable, aEndOfStream, aBytesRemaining); +} + +void TcpExample::HandleTcpDisconnectedCallback(otTcpEndpoint *aEndpoint, otTcpDisconnectedReason aReason) +{ + static_cast(otTcpEndpointGetContext(aEndpoint))->HandleTcpDisconnected(aEndpoint, aReason); +} + +otTcpIncomingConnectionAction TcpExample::HandleTcpAcceptReadyCallback(otTcpListener * aListener, + const otSockAddr *aPeer, + otTcpEndpoint ** aAcceptInto) +{ + return static_cast(otTcpListenerGetContext(aListener)) + ->HandleTcpAcceptReady(aListener, aPeer, aAcceptInto); +} + +void TcpExample::HandleTcpAcceptDoneCallback(otTcpListener * aListener, + otTcpEndpoint * aEndpoint, + const otSockAddr *aPeer) +{ + static_cast(otTcpListenerGetContext(aListener))->HandleTcpAcceptDone(aListener, aEndpoint, aPeer); +} + +void TcpExample::HandleTcpEstablished(otTcpEndpoint *aEndpoint) +{ + OT_UNUSED_VARIABLE(aEndpoint); + mInterpreter.OutputLine("TCP: Connection established"); +} + +void TcpExample::HandleTcpSendDone(otTcpEndpoint *aEndpoint, otLinkedBuffer *aData) +{ + OT_UNUSED_VARIABLE(aEndpoint); + + if (mBenchmarkBytesTotal == 0) + { + // If the benchmark encountered an error, we might end up here. So, + // tolerate some benchmark links finishing in this case. + if (aData == &mSendLink) + { + OT_ASSERT(mSendBusy); + mSendBusy = false; + } + } + else + { + OT_ASSERT(aData != &mSendLink); + mBenchmarkLinksLeft--; + if (mBenchmarkLinksLeft >= OT_ARRAY_LENGTH(mBenchmarkLinks)) + { + aData->mLength = sizeof(mSendBuffer); + if (otTcpSendByReference(&mEndpoint, aData, 0) != OT_ERROR_NONE) + { + mInterpreter.OutputLine("TCP Benchmark Failed"); + mBenchmarkBytesTotal = 0; + } + } + else if (mBenchmarkLinksLeft == 0) + { + uint32_t milliseconds = TimerMilli::GetNow() - mBenchmarkStart; + uint32_t thousandTimesGoodput = (1000 * (mBenchmarkBytesTotal << 3) + (milliseconds >> 1)) / milliseconds; + + mInterpreter.OutputLine("TCP Benchmark Complete: Transferred %u bytes in %u milliseconds", + static_cast(mBenchmarkBytesTotal), + static_cast(milliseconds)); + mInterpreter.OutputLine("TCP Goodput: %u.%03u kb/s", thousandTimesGoodput / 1000, + thousandTimesGoodput % 1000); + mBenchmarkBytesTotal = 0; + } + } +} + +void TcpExample::HandleTcpReceiveAvailable(otTcpEndpoint *aEndpoint, + size_t aBytesAvailable, + bool aEndOfStream, + size_t aBytesRemaining) +{ + OT_UNUSED_VARIABLE(aBytesRemaining); + OT_ASSERT(aEndpoint == &mEndpoint); + + if (aBytesAvailable > 0) + { + const otLinkedBuffer *data; + size_t totalReceived = 0; + + IgnoreError(otTcpReceiveByReference(aEndpoint, &data)); + for (; data != nullptr; data = data->mNext) + { + mInterpreter.OutputLine("TCP: Received %u bytes: %.*s", static_cast(data->mLength), + data->mLength, reinterpret_cast(data->mData)); + totalReceived += data->mLength; + } + OT_ASSERT(aBytesAvailable == totalReceived); + IgnoreReturnValue(otTcpCommitReceive(aEndpoint, totalReceived, 0)); + } + + if (aEndOfStream) + { + mInterpreter.OutputLine("TCP: Reached end of stream"); + } +} + +void TcpExample::HandleTcpDisconnected(otTcpEndpoint *aEndpoint, otTcpDisconnectedReason aReason) +{ + OT_UNUSED_VARIABLE(aEndpoint); + + switch (aReason) + { + case OT_TCP_DISCONNECTED_REASON_NORMAL: + mInterpreter.OutputLine("TCP: Disconnected"); + break; + case OT_TCP_DISCONNECTED_REASON_TIME_WAIT: + mInterpreter.OutputLine("TCP: Entered TIME-WAIT state"); + break; + case OT_TCP_DISCONNECTED_REASON_TIMED_OUT: + mInterpreter.OutputLine("TCP: Connection timed out"); + break; + case OT_TCP_DISCONNECTED_REASON_REFUSED: + mInterpreter.OutputLine("TCP: Connection refused"); + break; + case OT_TCP_DISCONNECTED_REASON_RESET: + mInterpreter.OutputLine("TCP: Connection reset"); + break; + } + + // We set this to false even for the TIME-WAIT state, so that we can reuse + // the active socket if an incoming connection comes in instead of waiting + // for the 2MSL timeout. + mEndpointConnected = false; + mSendBusy = false; + + // Mark the benchmark as inactive if the connction was disconnected. + if (mBenchmarkBytesTotal != 0) + { + mBenchmarkBytesTotal = 0; + mBenchmarkLinksLeft = 0; + } +} + +otTcpIncomingConnectionAction TcpExample::HandleTcpAcceptReady(otTcpListener * aListener, + const otSockAddr *aPeer, + otTcpEndpoint ** aAcceptInto) +{ + OT_UNUSED_VARIABLE(aListener); + + if (mEndpointConnected) + { + mInterpreter.OutputFormat("TCP: Ignoring incoming connection request from ["); + mInterpreter.OutputIp6Address(aPeer->mAddress); + mInterpreter.OutputLine("]:%u (active socket is busy)", static_cast(aPeer->mPort)); + + return OT_TCP_INCOMING_CONNECTION_ACTION_DEFER; + } + + *aAcceptInto = &mEndpoint; + return OT_TCP_INCOMING_CONNECTION_ACTION_ACCEPT; +} + +void TcpExample::HandleTcpAcceptDone(otTcpListener *aListener, otTcpEndpoint *aEndpoint, const otSockAddr *aPeer) +{ + OT_UNUSED_VARIABLE(aListener); + OT_UNUSED_VARIABLE(aEndpoint); + + mInterpreter.OutputFormat("Accepted connection from ["); + mInterpreter.OutputIp6Address(aPeer->mAddress); + mInterpreter.OutputLine("]:%u", static_cast(aPeer->mPort)); +} + +} // namespace Cli +} // namespace ot + +#endif // OPENTHREAD_CONFIG_TCP_ENABLE && OPENTHREAD_CONFIG_CLI_TCP_ENABLE diff --git a/src/cli/cli_tcp.hpp b/src/cli/cli_tcp.hpp new file mode 100644 index 000000000..b30926dcb --- /dev/null +++ b/src/cli/cli_tcp.hpp @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2021, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file contains definitions for a TCP CLI tool. + */ + +#ifndef CLI_TCP_EXAMPLE_HPP_ +#define CLI_TCP_EXAMPLE_HPP_ + +#include "openthread-core-config.h" + +#include + +#include "cli/cli_config.h" +#include "common/time.hpp" +#include "utils/lookup_table.hpp" +#include "utils/parse_cmdline.hpp" + +namespace ot { +namespace Cli { + +class Interpreter; + +/** + * This class implements a CLI-based TCP example. + * + */ +class TcpExample +{ +public: + using Arg = Utils::CmdLineParser::Arg; + + /** + * Constructor + * + * @param[in] aInterpreter The CLI interpreter. + * + */ + explicit TcpExample(Interpreter &aInterpreter); + + /** + * This mehtod interprets a list of CLI arguments. + * + * @param[in] aArgs An array of command line arguments. + * + */ + otError Process(Arg aArgs[]); + +private: + struct Command + { + const char *mName; + otError (TcpExample::*mHandler)(Arg aArgs[]); + }; + + otError ProcessHelp(Arg aArgs[]); + otError ProcessInit(Arg aArgs[]); + otError ProcessDeinit(Arg aArgs[]); + otError ProcessBind(Arg aArgs[]); + otError ProcessConnect(Arg aArgs[]); + otError ProcessSend(Arg aArgs[]); + otError ProcessBenchmark(Arg aArgs[]); + otError ProcessSendEnd(Arg aArgs[]); + otError ProcessAbort(Arg aArgs[]); + otError ProcessListen(Arg aArgs[]); + otError ProcessStopListening(Arg aArgs[]); + + static void HandleTcpEstablishedCallback(otTcpEndpoint *aEndpoint); + static void HandleTcpSendDoneCallback(otTcpEndpoint *aEndpoint, otLinkedBuffer *aData); + static void HandleTcpReceiveAvailableCallback(otTcpEndpoint *aEndpoint, + size_t aBytesAvailable, + bool aEndOfStream, + size_t aBytesRemaining); + static void HandleTcpDisconnectedCallback(otTcpEndpoint *aEndpoint, otTcpDisconnectedReason aReason); + static otTcpIncomingConnectionAction HandleTcpAcceptReadyCallback(otTcpListener * aListener, + const otSockAddr *aPeer, + otTcpEndpoint ** aAcceptInto); + static void HandleTcpAcceptDoneCallback(otTcpListener * aListener, + otTcpEndpoint * aEndpoint, + const otSockAddr *aPeer); + + void HandleTcpEstablished(otTcpEndpoint *aEndpoint); + void HandleTcpSendDone(otTcpEndpoint *aEndpoint, otLinkedBuffer *aData); + void HandleTcpReceiveAvailable(otTcpEndpoint *aEndpoint, + size_t aBytesAvailable, + bool aEndOfStream, + size_t aBytesRemaining); + void HandleTcpDisconnected(otTcpEndpoint *aEndpoint, otTcpDisconnectedReason aReason); + otTcpIncomingConnectionAction HandleTcpAcceptReady(otTcpListener * aListener, + const otSockAddr *aPeer, + otTcpEndpoint ** aAcceptInto); + void HandleTcpAcceptDone(otTcpListener *aListener, otTcpEndpoint *aEndpoint, const otSockAddr *aPeer); + + static constexpr Command sCommands[] = { + {"abort", &TcpExample::ProcessAbort}, + {"benchmark", &TcpExample::ProcessBenchmark}, + {"bind", &TcpExample::ProcessBind}, + {"connect", &TcpExample::ProcessConnect}, + {"deinit", &TcpExample::ProcessDeinit}, + {"help", &TcpExample::ProcessHelp}, + {"init", &TcpExample::ProcessInit}, + {"listen", &TcpExample::ProcessListen}, + {"send", &TcpExample::ProcessSend}, + {"sendend", &TcpExample::ProcessSendEnd}, + {"stoplistening", &TcpExample::ProcessStopListening}, + }; + + static_assert(Utils::LookupTable::IsSorted(sCommands), "Command Table is not sorted"); + + Interpreter &mInterpreter; + + otTcpEndpoint mEndpoint; + otTcpListener mListener; + + bool mInitialized; + bool mEndpointConnected; + bool mSendBusy; + + otLinkedBuffer mSendLink; + uint8_t mSendBuffer[OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH]; + uint8_t mReceiveBuffer[OPENTHREAD_CONFIG_CLI_TCP_RECEIVE_BUFFER_SIZE]; + + otLinkedBuffer mBenchmarkLinks[(sizeof(mReceiveBuffer) + sizeof(mSendBuffer) - 1) / sizeof(mSendBuffer)]; + uint32_t mBenchmarkBytesTotal; + uint32_t mBenchmarkLinksLeft; + TimeMilli mBenchmarkStart; +}; + +} // namespace Cli +} // namespace ot + +#endif // CLI_TCP_EXAMPLE_HPP_