[cli] add support for TCP in the CLI tool (#6770)

This commit is contained in:
Sam Kumar
2021-07-19 14:32:49 -07:00
committed by GitHub
parent 6e2b6fd03d
commit 6c03a1e962
11 changed files with 770 additions and 6 deletions
+1
View File
@@ -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)
+1 -1
View File
@@ -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
+28 -5
View File
@@ -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.
+2
View File
@@ -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",
+1
View File
@@ -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
)
+2
View File
@@ -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)
+10
View File
@@ -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);
+15
View File
@@ -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
+31
View File
@@ -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_
+520
View File
@@ -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
+159
View File
@@ -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_