mirror of
https://github.com/espressif/openthread.git
synced 2026-08-07 19:27:46 +00:00
[cli] add support for TCP in the CLI tool (#6770)
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -47,6 +47,9 @@
|
||||
#include <openthread/ip6.h>
|
||||
#include <openthread/link.h>
|
||||
#include <openthread/sntp.h>
|
||||
#if OPENTHREAD_CONFIG_TCP_ENABLE && OPENTHREAD_CONFIG_CLI_TCP_ENABLE
|
||||
#include <openthread/tcp.h>
|
||||
#endif
|
||||
#include <openthread/thread.h>
|
||||
#include <openthread/thread_ftd.h>
|
||||
#include <openthread/udp.h>
|
||||
@@ -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
|
||||
|
||||
@@ -37,6 +37,8 @@
|
||||
|
||||
#include "openthread-core-config.h"
|
||||
|
||||
#include <openthread/tcp.h>
|
||||
|
||||
#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_
|
||||
|
||||
@@ -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 <openthread/tcp.h>
|
||||
|
||||
#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<TcpExample *>(otTcpEndpointGetContext(aEndpoint))->HandleTcpEstablished(aEndpoint);
|
||||
}
|
||||
|
||||
void TcpExample::HandleTcpSendDoneCallback(otTcpEndpoint *aEndpoint, otLinkedBuffer *aData)
|
||||
{
|
||||
static_cast<TcpExample *>(otTcpEndpointGetContext(aEndpoint))->HandleTcpSendDone(aEndpoint, aData);
|
||||
}
|
||||
|
||||
void TcpExample::HandleTcpReceiveAvailableCallback(otTcpEndpoint *aEndpoint,
|
||||
size_t aBytesAvailable,
|
||||
bool aEndOfStream,
|
||||
size_t aBytesRemaining)
|
||||
{
|
||||
static_cast<TcpExample *>(otTcpEndpointGetContext(aEndpoint))
|
||||
->HandleTcpReceiveAvailable(aEndpoint, aBytesAvailable, aEndOfStream, aBytesRemaining);
|
||||
}
|
||||
|
||||
void TcpExample::HandleTcpDisconnectedCallback(otTcpEndpoint *aEndpoint, otTcpDisconnectedReason aReason)
|
||||
{
|
||||
static_cast<TcpExample *>(otTcpEndpointGetContext(aEndpoint))->HandleTcpDisconnected(aEndpoint, aReason);
|
||||
}
|
||||
|
||||
otTcpIncomingConnectionAction TcpExample::HandleTcpAcceptReadyCallback(otTcpListener * aListener,
|
||||
const otSockAddr *aPeer,
|
||||
otTcpEndpoint ** aAcceptInto)
|
||||
{
|
||||
return static_cast<TcpExample *>(otTcpListenerGetContext(aListener))
|
||||
->HandleTcpAcceptReady(aListener, aPeer, aAcceptInto);
|
||||
}
|
||||
|
||||
void TcpExample::HandleTcpAcceptDoneCallback(otTcpListener * aListener,
|
||||
otTcpEndpoint * aEndpoint,
|
||||
const otSockAddr *aPeer)
|
||||
{
|
||||
static_cast<TcpExample *>(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<unsigned int>(mBenchmarkBytesTotal),
|
||||
static_cast<unsigned int>(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<unsigned int>(data->mLength),
|
||||
data->mLength, reinterpret_cast<const char *>(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<unsigned int>(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<unsigned int>(aPeer->mPort));
|
||||
}
|
||||
|
||||
} // namespace Cli
|
||||
} // namespace ot
|
||||
|
||||
#endif // OPENTHREAD_CONFIG_TCP_ENABLE && OPENTHREAD_CONFIG_CLI_TCP_ENABLE
|
||||
@@ -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 <openthread/tcp.h>
|
||||
|
||||
#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_
|
||||
Reference in New Issue
Block a user