mirror of
https://github.com/espressif/openthread.git
synced 2026-06-05 21:14:49 +00:00
8dadae2ea1
We just updated the version from 14 to 16. But very unfortunately clang-format-16 was just deprecated recently. So this time we update the version to the latest available one. There are newer releases than 19 (like 20) but as I tested, sudo apt-get install -y clang-format-19 can work while 20 doesn't work. misc-include-cleaner in clang-tidy-19 exerts a very strict check which requires to directly include all headers for every symbols in the source file. However in our current code we intentionally use some indirect include. So this commit disables misc-include-cleaner.
1249 lines
39 KiB
C++
1249 lines
39 KiB
C++
/*
|
|
* 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/nat64.h>
|
|
#include <openthread/tcp.h>
|
|
|
|
#include "cli/cli.hpp"
|
|
#include "common/encoding.hpp"
|
|
#include "common/timer.hpp"
|
|
|
|
#if OPENTHREAD_CONFIG_TLS_ENABLE
|
|
#include <mbedtls/debug.h>
|
|
#include <mbedtls/ecjpake.h>
|
|
#include "crypto/mbedtls.hpp"
|
|
#endif
|
|
|
|
namespace ot {
|
|
namespace Cli {
|
|
|
|
#if OPENTHREAD_CONFIG_TLS_ENABLE
|
|
const int TcpExample::sCipherSuites[] = {MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8,
|
|
MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, 0};
|
|
#endif
|
|
|
|
TcpExample::TcpExample(otInstance *aInstance, OutputImplementer &aOutputImplementer)
|
|
: Utils(aInstance, aOutputImplementer)
|
|
, mInitialized(false)
|
|
, mEndpointConnected(false)
|
|
, mEndpointConnectedFastOpen(false)
|
|
, mSendBusy(false)
|
|
, mUseCircularSendBuffer(true)
|
|
, mUseTls(false)
|
|
, mTlsHandshakeComplete(false)
|
|
, mBenchmarkBytesTotal(0)
|
|
, mBenchmarkBytesUnsent(0)
|
|
, mBenchmarkTimeUsed(0)
|
|
{
|
|
mEndpointAndCircularSendBuffer.mEndpoint = &mEndpoint;
|
|
mEndpointAndCircularSendBuffer.mSendBuffer = &mSendBuffer;
|
|
}
|
|
|
|
#if OPENTHREAD_CONFIG_TLS_ENABLE
|
|
void TcpExample::MbedTlsDebugOutput(void *ctx, int level, const char *file, int line, const char *str)
|
|
{
|
|
TcpExample &tcpExample = *static_cast<TcpExample *>(ctx);
|
|
|
|
tcpExample.OutputLine("%s:%d:%d: %s", file, line, level, str);
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* @cli tcp init
|
|
* @code
|
|
* tcp init tls
|
|
* Done
|
|
* @endcode
|
|
* @cparam tcp init [@ca{mode}] [@ca{size}]
|
|
* * The `mode` has three possible values:
|
|
* * `tls`: Specifies that the TCP connection between two nodes should also
|
|
* use the TLS protocol on top of TCP. When two nodes communicate over TCP,
|
|
* both nodes must either use TLS or neither node should use TLS because
|
|
* a non-TLS endpoint cannot communicate with a TLS endpoint.
|
|
* * `linked` or `circular`: Either one of these options means that TLS
|
|
* is not to be used, and the specified buffering type should be used for TCP
|
|
* buffering. The behavior of `linked` and `circular` is identical. Examine the code
|
|
* for the differences between these two buffering types.
|
|
* Two endpoints of a TCP connection are not required to use the same buffering type.
|
|
* * The `size` parameter sets the size of the receive buffer to associate with the
|
|
* example TCP endpoint. If left unspecified, the maximum size is used. The
|
|
* maximum size is set in `OPENTHREAD_CONFIG_CLI_TCP_RECEIVE_BUFFER_SIZE`.
|
|
* @par
|
|
* Initializes the example TCP listener and the example TCP endpoint provided
|
|
* by the `tcp` CLI.
|
|
* @sa otTcpListenerInitialize
|
|
* @sa otTcpEndpointInitialize
|
|
*/
|
|
template <> otError TcpExample::Process<Cmd("init")>(Arg aArgs[])
|
|
{
|
|
otError error = OT_ERROR_NONE;
|
|
size_t receiveBufferSize;
|
|
|
|
VerifyOrExit(!mInitialized, error = OT_ERROR_ALREADY);
|
|
|
|
if (aArgs[0].IsEmpty())
|
|
{
|
|
mUseCircularSendBuffer = true;
|
|
mUseTls = false;
|
|
receiveBufferSize = sizeof(mReceiveBufferBytes);
|
|
}
|
|
else
|
|
{
|
|
if (aArgs[0] == "linked")
|
|
{
|
|
mUseCircularSendBuffer = false;
|
|
mUseTls = false;
|
|
}
|
|
else if (aArgs[0] == "circular")
|
|
{
|
|
mUseCircularSendBuffer = true;
|
|
mUseTls = false;
|
|
}
|
|
#if OPENTHREAD_CONFIG_TLS_ENABLE
|
|
else if (aArgs[0] == "tls")
|
|
{
|
|
mUseCircularSendBuffer = true;
|
|
mUseTls = true;
|
|
|
|
mbedtls_x509_crt_init(&mSrvCert);
|
|
mbedtls_pk_init(&mPKey);
|
|
|
|
mbedtls_ssl_init(&mSslContext);
|
|
mbedtls_ssl_config_init(&mSslConfig);
|
|
mbedtls_ssl_conf_rng(&mSslConfig, Crypto::MbedTls::CryptoSecurePrng, nullptr);
|
|
mbedtls_ssl_conf_authmode(&mSslConfig, MBEDTLS_SSL_VERIFY_NONE);
|
|
mbedtls_ssl_conf_ciphersuites(&mSslConfig, sCipherSuites);
|
|
|
|
#if (MBEDTLS_VERSION_NUMBER >= 0x03020000)
|
|
mbedtls_ssl_conf_min_tls_version(&mSslConfig, MBEDTLS_SSL_VERSION_TLS1_2);
|
|
mbedtls_ssl_conf_max_tls_version(&mSslConfig, MBEDTLS_SSL_VERSION_TLS1_2);
|
|
#else
|
|
mbedtls_ssl_conf_min_version(&mSslConfig, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3);
|
|
mbedtls_ssl_conf_max_version(&mSslConfig, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3);
|
|
#endif
|
|
|
|
#if (MBEDTLS_VERSION_NUMBER >= 0x03000000)
|
|
#include "crypto/mbedtls.hpp"
|
|
int rv = mbedtls_pk_parse_key(&mPKey, reinterpret_cast<const unsigned char *>(sSrvKey), sSrvKeyLength,
|
|
nullptr, 0, Crypto::MbedTls::CryptoSecurePrng, nullptr);
|
|
#else
|
|
int rv = mbedtls_pk_parse_key(&mPKey, reinterpret_cast<const unsigned char *>(sSrvKey), sSrvKeyLength,
|
|
nullptr, 0);
|
|
#endif
|
|
if (rv != 0)
|
|
{
|
|
OutputLine("mbedtls_pk_parse_key returned %d", rv);
|
|
}
|
|
|
|
rv = mbedtls_x509_crt_parse(&mSrvCert, reinterpret_cast<const unsigned char *>(sSrvPem), sSrvPemLength);
|
|
if (rv != 0)
|
|
{
|
|
OutputLine("mbedtls_x509_crt_parse (1) returned %d", rv);
|
|
}
|
|
rv = mbedtls_x509_crt_parse(&mSrvCert, reinterpret_cast<const unsigned char *>(sCasPem), sCasPemLength);
|
|
if (rv != 0)
|
|
{
|
|
OutputLine("mbedtls_x509_crt_parse (2) returned %d", rv);
|
|
}
|
|
rv = mbedtls_ssl_setup(&mSslContext, &mSslConfig);
|
|
if (rv != 0)
|
|
{
|
|
OutputLine("mbedtls_ssl_setup returned %d", rv);
|
|
}
|
|
}
|
|
#endif // OPENTHREAD_CONFIG_TLS_ENABLE
|
|
else
|
|
{
|
|
ExitNow(error = OT_ERROR_INVALID_ARGS);
|
|
}
|
|
|
|
if (aArgs[1].IsEmpty())
|
|
{
|
|
receiveBufferSize = sizeof(mReceiveBufferBytes);
|
|
}
|
|
else
|
|
{
|
|
uint32_t windowSize;
|
|
|
|
SuccessOrExit(error = aArgs[1].ParseAsUint32(windowSize));
|
|
|
|
receiveBufferSize = windowSize + ((windowSize + 7) >> 3);
|
|
VerifyOrExit(receiveBufferSize <= sizeof(mReceiveBufferBytes) && receiveBufferSize != 0,
|
|
error = OT_ERROR_INVALID_ARGS);
|
|
}
|
|
}
|
|
|
|
otTcpCircularSendBufferInitialize(&mSendBuffer, mSendBufferBytes, sizeof(mSendBufferBytes));
|
|
|
|
{
|
|
otTcpEndpointInitializeArgs endpointArgs;
|
|
|
|
ClearAllBytes(endpointArgs);
|
|
endpointArgs.mEstablishedCallback = HandleTcpEstablishedCallback;
|
|
|
|
if (mUseCircularSendBuffer)
|
|
{
|
|
endpointArgs.mForwardProgressCallback = HandleTcpForwardProgressCallback;
|
|
}
|
|
else
|
|
{
|
|
endpointArgs.mSendDoneCallback = HandleTcpSendDoneCallback;
|
|
}
|
|
|
|
endpointArgs.mReceiveAvailableCallback = HandleTcpReceiveAvailableCallback;
|
|
endpointArgs.mDisconnectedCallback = HandleTcpDisconnectedCallback;
|
|
endpointArgs.mContext = this;
|
|
endpointArgs.mReceiveBuffer = mReceiveBufferBytes;
|
|
endpointArgs.mReceiveBufferSize = receiveBufferSize;
|
|
|
|
SuccessOrExit(error = otTcpEndpointInitialize(GetInstancePtr(), &mEndpoint, &endpointArgs));
|
|
}
|
|
|
|
{
|
|
otTcpListenerInitializeArgs listenerArgs;
|
|
|
|
ClearAllBytes(listenerArgs);
|
|
listenerArgs.mAcceptReadyCallback = HandleTcpAcceptReadyCallback;
|
|
listenerArgs.mAcceptDoneCallback = HandleTcpAcceptDoneCallback;
|
|
listenerArgs.mContext = this;
|
|
|
|
error = otTcpListenerInitialize(GetInstancePtr(), &mListener, &listenerArgs);
|
|
|
|
if (error != OT_ERROR_NONE)
|
|
{
|
|
IgnoreReturnValue(otTcpEndpointDeinitialize(&mEndpoint));
|
|
ExitNow();
|
|
}
|
|
}
|
|
|
|
mInitialized = true;
|
|
|
|
exit:
|
|
if (error != OT_ERROR_NONE)
|
|
{
|
|
#if OPENTHREAD_CONFIG_TLS_ENABLE
|
|
if (mUseTls)
|
|
{
|
|
mbedtls_ssl_config_free(&mSslConfig);
|
|
mbedtls_ssl_free(&mSslContext);
|
|
|
|
mbedtls_pk_free(&mPKey);
|
|
mbedtls_x509_crt_free(&mSrvCert);
|
|
}
|
|
#endif // OPENTHREAD_CONFIG_TLS_ENABLE
|
|
|
|
otTcpCircularSendBufferForceDiscardAll(&mSendBuffer);
|
|
OT_UNUSED_VARIABLE(otTcpCircularSendBufferDeinitialize(&mSendBuffer));
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
/**
|
|
* @cli tcp deinit
|
|
* @code
|
|
* tcp deinit
|
|
* Done
|
|
* @endcode
|
|
* @par api_copy
|
|
* #otTcpEndpointDeinitialize
|
|
*/
|
|
template <> otError TcpExample::Process<Cmd("deinit")>(Arg aArgs[])
|
|
{
|
|
otError error = OT_ERROR_NONE;
|
|
otError endpointError;
|
|
otError bufferError;
|
|
otError listenerError;
|
|
|
|
VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
|
|
VerifyOrExit(mInitialized, error = OT_ERROR_INVALID_STATE);
|
|
|
|
#if OPENTHREAD_CONFIG_TLS_ENABLE
|
|
if (mUseTls)
|
|
{
|
|
mbedtls_ssl_config_free(&mSslConfig);
|
|
mbedtls_ssl_free(&mSslContext);
|
|
|
|
mbedtls_pk_free(&mPKey);
|
|
mbedtls_x509_crt_free(&mSrvCert);
|
|
|
|
mUseTls = false;
|
|
}
|
|
#endif
|
|
|
|
endpointError = otTcpEndpointDeinitialize(&mEndpoint);
|
|
mSendBusy = false;
|
|
|
|
otTcpCircularSendBufferForceDiscardAll(&mSendBuffer);
|
|
bufferError = otTcpCircularSendBufferDeinitialize(&mSendBuffer);
|
|
|
|
listenerError = otTcpListenerDeinitialize(&mListener);
|
|
mInitialized = false;
|
|
|
|
SuccessOrExit(error = endpointError);
|
|
SuccessOrExit(error = bufferError);
|
|
SuccessOrExit(error = listenerError);
|
|
|
|
exit:
|
|
return error;
|
|
}
|
|
|
|
/**
|
|
* @cli tcp bind
|
|
* @code
|
|
* tcp bind :: 30000
|
|
* Done
|
|
* @endcode
|
|
* @cparam tcp bind @ca{ip} @ca{port}
|
|
* * `ip`: IPv6 address to bind to. If you wish to have the TCP/IPv6 stack assign
|
|
* the binding IPv6 address, use the unspecified IPv6 address: `::`.
|
|
* * `port`: TCP port number to bind to.
|
|
* @par
|
|
* Associates an IPv6 address and a port to the example TCP endpoint provided by
|
|
* the `tcp` CLI. Associating the TCP endpoint to an IPv6
|
|
* address and port is referred to as "naming the TCP endpoint." This binds the
|
|
* endpoint for communication. @moreinfo{@tcp}.
|
|
* @sa otTcpBind
|
|
*/
|
|
template <> otError TcpExample::Process<Cmd("bind")>(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;
|
|
}
|
|
|
|
/**
|
|
* @cli tcp connect
|
|
* @code
|
|
* tcp connect fe80:0:0:0:a8df:580a:860:ffa4 30000
|
|
* Done
|
|
* TCP: Connection established
|
|
* @endcode
|
|
* @code
|
|
* tcp connect 172.17.0.1 1234
|
|
* Connecting to synthesized IPv6 address: fdde:ad00:beef:2:0:0:ac11:1
|
|
* Done
|
|
* @endcode
|
|
* @cparam tcp connect @ca{ip} @ca{port} [@ca{fastopen}]
|
|
* * `ip`: IP address of the peer The address can be an IPv4 address,
|
|
* which gets synthesized to an IPv6 address using the preferred
|
|
* NAT64 prefix from the network data. The command returns `InvalidState`
|
|
* when the preferred NAT64 prefix is unavailable.
|
|
* * `port`: TCP port number of the peer.
|
|
* * `fastopen`: This parameter is optional. If set to `fast`, TCP Fast Open is enabled
|
|
* for this connection. Otherwise, if this parameter is set to `slow` or not used,
|
|
* TCP Fast Open is disabled.
|
|
* @par
|
|
* Establishes a connection with the specified peer.
|
|
* @par
|
|
* If the connection establishment is successful, the resulting TCP connection
|
|
* is associated with the example TCP endpoint. @moreinfo{@tcp}.
|
|
* @sa otTcpConnect
|
|
*/
|
|
template <> otError TcpExample::Process<Cmd("connect")>(Arg aArgs[])
|
|
{
|
|
otError error;
|
|
otSockAddr sockaddr;
|
|
bool nat64Synth;
|
|
uint32_t flags;
|
|
|
|
VerifyOrExit(mInitialized, error = OT_ERROR_INVALID_STATE);
|
|
|
|
SuccessOrExit(error = ParseToIp6Address(GetInstancePtr(), aArgs[0], sockaddr.mAddress, nat64Synth));
|
|
|
|
if (nat64Synth)
|
|
{
|
|
OutputFormat("Connecting to synthesized IPv6 address: ");
|
|
OutputIp6AddressLine(sockaddr.mAddress);
|
|
}
|
|
|
|
SuccessOrExit(error = aArgs[1].ParseAsUint16(sockaddr.mPort));
|
|
|
|
if (aArgs[2].IsEmpty())
|
|
{
|
|
flags = OT_TCP_CONNECT_NO_FAST_OPEN;
|
|
}
|
|
else
|
|
{
|
|
if (aArgs[2] == "slow")
|
|
{
|
|
flags = OT_TCP_CONNECT_NO_FAST_OPEN;
|
|
}
|
|
else if (aArgs[2] == "fast")
|
|
{
|
|
flags = 0;
|
|
}
|
|
else
|
|
{
|
|
ExitNow(error = OT_ERROR_INVALID_ARGS);
|
|
}
|
|
|
|
VerifyOrExit(aArgs[3].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
|
|
}
|
|
|
|
#if OPENTHREAD_CONFIG_TLS_ENABLE
|
|
if (mUseTls)
|
|
{
|
|
int rv = mbedtls_ssl_config_defaults(&mSslConfig, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM,
|
|
MBEDTLS_SSL_PRESET_DEFAULT);
|
|
if (rv != 0)
|
|
{
|
|
OutputLine("mbedtls_ssl_config_defaults returned %d", rv);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
SuccessOrExit(error = otTcpConnect(&mEndpoint, &sockaddr, flags));
|
|
mEndpointConnected = true;
|
|
mEndpointConnectedFastOpen = ((flags & OT_TCP_CONNECT_NO_FAST_OPEN) == 0);
|
|
|
|
#if OPENTHREAD_CONFIG_TLS_ENABLE
|
|
if (mUseTls && mEndpointConnectedFastOpen)
|
|
{
|
|
PrepareTlsHandshake();
|
|
ContinueTlsHandshake();
|
|
}
|
|
#endif
|
|
|
|
exit:
|
|
return error;
|
|
}
|
|
|
|
/**
|
|
* @cli tcp send
|
|
* @code
|
|
* tcp send hello
|
|
* Done
|
|
* @endcode
|
|
* @code
|
|
* tcp send -x 68656c6c6f
|
|
* Done
|
|
* @endcode
|
|
* @cparam tcp send [@ca{type}] @ca{message}
|
|
* The `message` parameter contains the message you want to send to the
|
|
* remote TCP endpoint.
|
|
* If `type` is `-x`, then
|
|
* binary data in hexadecimal representation is given in the `message` parameter.
|
|
* @par
|
|
* Sends data over the TCP connection associated with the example TCP endpoint
|
|
* that is provided with the `tcp` CLI. @moreinfo{@tcp}.
|
|
*/
|
|
template <> otError TcpExample::Process<Cmd("send")>(Arg aArgs[])
|
|
{
|
|
static constexpr uint16_t kBufferSizeForHexData = 128;
|
|
|
|
otError error;
|
|
uint16_t dataLen;
|
|
uint8_t *data;
|
|
uint8_t buf[kBufferSizeForHexData];
|
|
|
|
VerifyOrExit(mInitialized, error = OT_ERROR_INVALID_STATE);
|
|
VerifyOrExit(mBenchmarkBytesTotal == 0, error = OT_ERROR_BUSY);
|
|
VerifyOrExit(!aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
|
|
|
|
if (aArgs[0] == "-x" && !aArgs[1].IsEmpty())
|
|
{
|
|
// Binary hex data payload
|
|
dataLen = sizeof(buf);
|
|
data = &buf[0];
|
|
SuccessOrExit(error = aArgs[1].ParseAsHexString(dataLen, buf));
|
|
}
|
|
else
|
|
{
|
|
VerifyOrExit(aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
|
|
dataLen = aArgs[0].GetLength();
|
|
data = reinterpret_cast<uint8_t *>(aArgs[0].GetCString());
|
|
}
|
|
|
|
if (mUseCircularSendBuffer)
|
|
{
|
|
#if OPENTHREAD_CONFIG_TLS_ENABLE
|
|
if (mUseTls)
|
|
{
|
|
int rv = mbedtls_ssl_write(&mSslContext, data, dataLen);
|
|
|
|
if (rv < 0 && rv != MBEDTLS_ERR_SSL_WANT_WRITE && rv != MBEDTLS_ERR_SSL_WANT_READ)
|
|
{
|
|
ExitNow(error = OT_ERROR_FAILED);
|
|
}
|
|
|
|
error = OT_ERROR_NONE;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
size_t written;
|
|
|
|
SuccessOrExit(error = otTcpCircularSendBufferWrite(&mEndpoint, &mSendBuffer, data, dataLen, &written, 0));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
VerifyOrExit(!mSendBusy, error = OT_ERROR_BUSY);
|
|
|
|
mSendLink.mNext = nullptr;
|
|
mSendLink.mData = mSendBufferBytes;
|
|
mSendLink.mLength = OT_MIN(dataLen, sizeof(mSendBufferBytes));
|
|
memcpy(mSendBufferBytes, data, mSendLink.mLength);
|
|
|
|
SuccessOrExit(error = otTcpSendByReference(&mEndpoint, &mSendLink, 0));
|
|
mSendBusy = true;
|
|
}
|
|
|
|
exit:
|
|
return error;
|
|
}
|
|
|
|
template <> otError TcpExample::Process<Cmd("benchmark")>(Arg aArgs[])
|
|
{
|
|
otError error = OT_ERROR_NONE;
|
|
|
|
/**
|
|
* @cli tcp benchmark result
|
|
* @code
|
|
* tcp benchmark result
|
|
* TCP Benchmark Status: Ongoing
|
|
* Done
|
|
* @endcode
|
|
* @code
|
|
* tcp benchmark result
|
|
* TCP Benchmark Status: Completed
|
|
* TCP Benchmark Complete: Transferred 73728 bytes in 7056 milliseconds
|
|
* TCP Goodput: 83.592 kb/s
|
|
* @endcode
|
|
* @par
|
|
* Shows the latest result of the TCP benchmark test. Possible status values:
|
|
* * Ongoing
|
|
* * Completed
|
|
* * Untested
|
|
* @par
|
|
* This command is primarily intended for creating scripts that automate
|
|
* the TCP benchmark test.
|
|
*/
|
|
if (aArgs[0] == "result")
|
|
{
|
|
OutputFormat("TCP Benchmark Status: ");
|
|
|
|
if (mBenchmarkBytesTotal != 0)
|
|
{
|
|
OutputLine("Ongoing");
|
|
}
|
|
else if (mBenchmarkTimeUsed != 0)
|
|
{
|
|
OutputLine("Completed");
|
|
OutputBenchmarkResult();
|
|
}
|
|
else
|
|
{
|
|
OutputLine("Untested");
|
|
}
|
|
}
|
|
/**
|
|
* @cli tcp benchmark run
|
|
* @code
|
|
* tcp benchmark run
|
|
* Done
|
|
* TCP Benchmark Complete: Transferred 73728 bytes in 7233 milliseconds
|
|
* TCP Goodput: 81.546 kb/s
|
|
* @endcode
|
|
* @cparam tcp benchmark run [@ca{size}]
|
|
* Use the `size` parameter to specify the number of bytes to send
|
|
* for the benchmark. If you do not use the `size` parameter, the default
|
|
* value (`OPENTHREAD_CONFIG_CLI_TCP_DEFAULT_BENCHMARK_SIZE`) is used.
|
|
* @par
|
|
* Transfers the specified number of bytes using the TCP connection
|
|
* currently associated with the example TCP endpoint provided by the `tcp` CLI.
|
|
* @note You must establish a TCP connection before you run this command.
|
|
*/
|
|
else if (aArgs[0] == "run")
|
|
{
|
|
VerifyOrExit(!mSendBusy, error = OT_ERROR_BUSY);
|
|
VerifyOrExit(mBenchmarkBytesTotal == 0, error = OT_ERROR_BUSY);
|
|
|
|
if (aArgs[1].IsEmpty())
|
|
{
|
|
mBenchmarkBytesTotal = OPENTHREAD_CONFIG_CLI_TCP_DEFAULT_BENCHMARK_SIZE;
|
|
}
|
|
else
|
|
{
|
|
SuccessOrExit(error = aArgs[1].ParseAsUint32(mBenchmarkBytesTotal));
|
|
VerifyOrExit(mBenchmarkBytesTotal != 0, error = OT_ERROR_INVALID_ARGS);
|
|
}
|
|
|
|
VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
|
|
|
|
mBenchmarkStart = TimerMilli::GetNow();
|
|
mBenchmarkBytesUnsent = mBenchmarkBytesTotal;
|
|
|
|
if (mUseCircularSendBuffer)
|
|
{
|
|
SuccessOrExit(error = ContinueBenchmarkCircularSend());
|
|
}
|
|
else
|
|
{
|
|
uint32_t benchmarkLinksLeft =
|
|
(mBenchmarkBytesTotal + sizeof(mSendBufferBytes) - 1) / sizeof(mSendBufferBytes);
|
|
uint32_t toSendOut = OT_MIN(OT_ARRAY_LENGTH(mBenchmarkLinks), benchmarkLinksLeft);
|
|
|
|
/* We could also point the linked buffers directly to sBenchmarkData. */
|
|
memset(mSendBufferBytes, 'a', sizeof(mSendBufferBytes));
|
|
|
|
for (uint32_t i = 0; i != toSendOut; i++)
|
|
{
|
|
mBenchmarkLinks[i].mNext = nullptr;
|
|
mBenchmarkLinks[i].mData = mSendBufferBytes;
|
|
mBenchmarkLinks[i].mLength = sizeof(mSendBufferBytes);
|
|
|
|
if (i == 0 && mBenchmarkBytesTotal % sizeof(mSendBufferBytes) != 0)
|
|
{
|
|
mBenchmarkLinks[i].mLength = mBenchmarkBytesTotal % sizeof(mSendBufferBytes);
|
|
}
|
|
|
|
error = otTcpSendByReference(&mEndpoint, &mBenchmarkLinks[i],
|
|
i == toSendOut - 1 ? 0 : OT_TCP_SEND_MORE_TO_COME);
|
|
VerifyOrExit(error == OT_ERROR_NONE, mBenchmarkBytesTotal = 0);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
error = OT_ERROR_INVALID_ARGS;
|
|
}
|
|
|
|
exit:
|
|
return error;
|
|
}
|
|
|
|
/**
|
|
* @cli tcp sendend
|
|
* @code
|
|
* tcp sendend
|
|
* Done
|
|
* @endcode
|
|
* @par
|
|
* Sends the "end of stream" signal over the TCP connection
|
|
* associated with the example TCP endpoint provided by the `tcp` CLI. This
|
|
* alerts the peer that it will not receive any more data over this TCP connection.
|
|
* @sa otTcpSendEndOfStream
|
|
*/
|
|
template <> otError TcpExample::Process<Cmd("sendend")>(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;
|
|
}
|
|
|
|
/**
|
|
* @cli tcp abort
|
|
* @code
|
|
* tcp abort
|
|
* TCP: Connection reset
|
|
* Done
|
|
* @endcode
|
|
* @par
|
|
* Unceremoniously ends the TCP connection associated with the
|
|
* example TCP endpoint, transitioning the TCP endpoint to the closed state.
|
|
* @sa otTcpAbort
|
|
*/
|
|
template <> otError TcpExample::Process<Cmd("abort")>(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;
|
|
mEndpointConnectedFastOpen = false;
|
|
|
|
exit:
|
|
return error;
|
|
}
|
|
|
|
/**
|
|
* @cli tcp listen
|
|
* @code
|
|
* tcp listen :: 30000
|
|
* Done
|
|
* @endcode
|
|
* @cparam tcp listen @ca{ip} @ca{port}
|
|
* The following parameters are required:
|
|
* * `ip`: IPv6 address or the unspecified IPv6 address (`::`) of the example
|
|
* TCP listener provided by the `tcp` CLI.
|
|
* * `port`: TCP port of the example TCP listener.
|
|
* If no TCP connection is associated with the example TCP endpoint, then any
|
|
* incoming connections matching the specified IPv6 address and port are accepted
|
|
* and are associated with the example TCP endpoint.
|
|
* @par
|
|
* Uses the example TCP listener to listen for incoming connections on the
|
|
* specified IPv6 address and port. @moreinfo{@tcp}.
|
|
* @sa otTcpListen
|
|
*/
|
|
template <> otError TcpExample::Process<Cmd("listen")>(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;
|
|
}
|
|
|
|
/**
|
|
* @cli tcp stoplistening
|
|
* @code
|
|
* tcp stoplistening
|
|
* Done
|
|
* @endcode
|
|
* @par
|
|
* Instructs the example TCP listener to stop listening for incoming TCP connections.
|
|
* @sa otTcpStopListening
|
|
*/
|
|
template <> otError TcpExample::Process<Cmd("stoplistening")>(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[])
|
|
{
|
|
#define CmdEntry(aCommandString) {aCommandString, &TcpExample::Process<Cmd(aCommandString)>}
|
|
|
|
static constexpr Command kCommands[] = {
|
|
CmdEntry("abort"), CmdEntry("benchmark"), CmdEntry("bind"), CmdEntry("connect"), CmdEntry("deinit"),
|
|
CmdEntry("init"), CmdEntry("listen"), CmdEntry("send"), CmdEntry("sendend"), CmdEntry("stoplistening"),
|
|
};
|
|
|
|
static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted");
|
|
|
|
otError error = OT_ERROR_INVALID_COMMAND;
|
|
const Command *command;
|
|
|
|
if (aArgs[0].IsEmpty() || (aArgs[0] == "help"))
|
|
{
|
|
OutputCommandTable(kCommands);
|
|
ExitNow(error = aArgs[0].IsEmpty() ? error : OT_ERROR_NONE);
|
|
}
|
|
|
|
command = BinarySearch::Find(aArgs[0].GetCString(), kCommands);
|
|
VerifyOrExit(command != nullptr);
|
|
|
|
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::HandleTcpForwardProgressCallback(otTcpEndpoint *aEndpoint, size_t aInSendBuffer, size_t aBacklog)
|
|
{
|
|
static_cast<TcpExample *>(otTcpEndpointGetContext(aEndpoint))
|
|
->HandleTcpForwardProgress(aEndpoint, aInSendBuffer, aBacklog);
|
|
}
|
|
|
|
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);
|
|
OutputLine("TCP: Connection established");
|
|
#if OPENTHREAD_CONFIG_TLS_ENABLE
|
|
if (mUseTls && !mEndpointConnectedFastOpen)
|
|
{
|
|
PrepareTlsHandshake();
|
|
ContinueTlsHandshake();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void TcpExample::HandleTcpSendDone(otTcpEndpoint *aEndpoint, otLinkedBuffer *aData)
|
|
{
|
|
OT_UNUSED_VARIABLE(aEndpoint);
|
|
OT_ASSERT(!mUseCircularSendBuffer); // this callback is not used when using the circular send buffer
|
|
|
|
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);
|
|
OT_ASSERT(mBenchmarkBytesUnsent >= aData->mLength);
|
|
|
|
mBenchmarkBytesUnsent -= aData->mLength; // could be less than sizeof(mSendBufferBytes) for the first link
|
|
|
|
if (mBenchmarkBytesUnsent >= OT_ARRAY_LENGTH(mBenchmarkLinks) * sizeof(mSendBufferBytes))
|
|
{
|
|
aData->mLength = sizeof(mSendBufferBytes);
|
|
|
|
if (otTcpSendByReference(&mEndpoint, aData, 0) != OT_ERROR_NONE)
|
|
{
|
|
OutputLine("TCP Benchmark Failed");
|
|
mBenchmarkBytesTotal = 0;
|
|
}
|
|
}
|
|
else if (mBenchmarkBytesUnsent == 0)
|
|
{
|
|
CompleteBenchmark();
|
|
}
|
|
}
|
|
}
|
|
|
|
void TcpExample::HandleTcpForwardProgress(otTcpEndpoint *aEndpoint, size_t aInSendBuffer, size_t aBacklog)
|
|
{
|
|
OT_UNUSED_VARIABLE(aEndpoint);
|
|
OT_UNUSED_VARIABLE(aBacklog);
|
|
OT_ASSERT(mUseCircularSendBuffer); // this callback is only used when using the circular send buffer
|
|
|
|
otTcpCircularSendBufferHandleForwardProgress(&mSendBuffer, aInSendBuffer);
|
|
|
|
#if OPENTHREAD_CONFIG_TLS_ENABLE
|
|
if (mUseTls)
|
|
{
|
|
ContinueTlsHandshake();
|
|
}
|
|
#endif
|
|
|
|
/* Handle case where we're in a benchmark. */
|
|
if (mBenchmarkBytesTotal != 0)
|
|
{
|
|
if (mBenchmarkBytesUnsent != 0)
|
|
{
|
|
/* Continue sending out data if there's data we haven't sent. */
|
|
IgnoreError(ContinueBenchmarkCircularSend());
|
|
}
|
|
else if (aInSendBuffer == 0)
|
|
{
|
|
/* Handle case where all data is sent out and the send buffer has drained. */
|
|
CompleteBenchmark();
|
|
}
|
|
}
|
|
}
|
|
|
|
void TcpExample::HandleTcpReceiveAvailable(otTcpEndpoint *aEndpoint,
|
|
size_t aBytesAvailable,
|
|
bool aEndOfStream,
|
|
size_t aBytesRemaining)
|
|
{
|
|
OT_UNUSED_VARIABLE(aBytesRemaining);
|
|
OT_ASSERT(aEndpoint == &mEndpoint);
|
|
|
|
/* If we get data before the handshake completes, then this is a TFO connection. */
|
|
if (!mEndpointConnected)
|
|
{
|
|
mEndpointConnected = true;
|
|
mEndpointConnectedFastOpen = true;
|
|
|
|
#if OPENTHREAD_CONFIG_TLS_ENABLE
|
|
if (mUseTls)
|
|
{
|
|
PrepareTlsHandshake();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if OPENTHREAD_CONFIG_TLS_ENABLE
|
|
if (mUseTls && ContinueTlsHandshake())
|
|
{
|
|
ExitNow();
|
|
}
|
|
#endif
|
|
|
|
if ((mTlsHandshakeComplete || !mUseTls) && aBytesAvailable > 0)
|
|
{
|
|
#if OPENTHREAD_CONFIG_TLS_ENABLE
|
|
if (mUseTls)
|
|
{
|
|
uint8_t buffer[500];
|
|
|
|
for (;;)
|
|
{
|
|
int rv = mbedtls_ssl_read(&mSslContext, buffer, sizeof(buffer));
|
|
|
|
if (rv < 0)
|
|
{
|
|
if (rv == MBEDTLS_ERR_SSL_WANT_READ)
|
|
{
|
|
break;
|
|
}
|
|
|
|
OutputLine("TLS receive failure: %d", rv);
|
|
}
|
|
else
|
|
{
|
|
OutputLine("TLS: Received %u bytes: %.*s", static_cast<unsigned>(rv), rv,
|
|
reinterpret_cast<const char *>(buffer));
|
|
}
|
|
}
|
|
OutputLine("(TCP: Received %u bytes)", static_cast<unsigned>(aBytesAvailable));
|
|
}
|
|
else
|
|
#endif // OPENTHREAD_CONFIG_TLS_ENABLE
|
|
{
|
|
const otLinkedBuffer *data;
|
|
size_t totalReceived = 0;
|
|
|
|
IgnoreError(otTcpReceiveByReference(aEndpoint, &data));
|
|
|
|
for (; data != nullptr; data = data->mNext)
|
|
{
|
|
OutputLine("TCP: Received %u bytes: %.*s", static_cast<unsigned>(data->mLength),
|
|
static_cast<unsigned>(data->mLength), reinterpret_cast<const char *>(data->mData));
|
|
totalReceived += data->mLength;
|
|
}
|
|
|
|
OT_ASSERT(aBytesAvailable == totalReceived);
|
|
IgnoreReturnValue(otTcpCommitReceive(aEndpoint, totalReceived, 0));
|
|
}
|
|
}
|
|
|
|
if (aEndOfStream)
|
|
{
|
|
OutputLine("TCP: Reached end of stream");
|
|
}
|
|
|
|
ExitNow();
|
|
|
|
exit:
|
|
return;
|
|
}
|
|
|
|
void TcpExample::HandleTcpDisconnected(otTcpEndpoint *aEndpoint, otTcpDisconnectedReason aReason)
|
|
{
|
|
static const char *const kReasonStrings[] = {
|
|
"Disconnected", // (0) OT_TCP_DISCONNECTED_REASON_NORMAL
|
|
"Connection refused", // (1) OT_TCP_DISCONNECTED_REASON_REFUSED
|
|
"Connection reset", // (2) OT_TCP_DISCONNECTED_REASON_RESET
|
|
"Entered TIME-WAIT state", // (3) OT_TCP_DISCONNECTED_REASON_TIME_WAIT
|
|
"Connection timed out", // (4) OT_TCP_DISCONNECTED_REASON_TIMED_OUT
|
|
};
|
|
|
|
OT_UNUSED_VARIABLE(aEndpoint);
|
|
|
|
static_assert(0 == OT_TCP_DISCONNECTED_REASON_NORMAL, "OT_TCP_DISCONNECTED_REASON_NORMAL value is incorrect");
|
|
static_assert(1 == OT_TCP_DISCONNECTED_REASON_REFUSED, "OT_TCP_DISCONNECTED_REASON_REFUSED value is incorrect");
|
|
static_assert(2 == OT_TCP_DISCONNECTED_REASON_RESET, "OT_TCP_DISCONNECTED_REASON_RESET value is incorrect");
|
|
static_assert(3 == OT_TCP_DISCONNECTED_REASON_TIME_WAIT, "OT_TCP_DISCONNECTED_REASON_TIME_WAIT value is incorrect");
|
|
static_assert(4 == OT_TCP_DISCONNECTED_REASON_TIMED_OUT, "OT_TCP_DISCONNECTED_REASON_TIMED_OUT value is incorrect");
|
|
|
|
OutputLine("TCP: %s", Stringify(aReason, kReasonStrings));
|
|
|
|
#if OPENTHREAD_CONFIG_TLS_ENABLE
|
|
if (mUseTls)
|
|
{
|
|
mbedtls_ssl_session_reset(&mSslContext);
|
|
}
|
|
#endif
|
|
|
|
// 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;
|
|
mEndpointConnectedFastOpen = false;
|
|
mSendBusy = false;
|
|
|
|
// Mark the benchmark as inactive if the connection was disconnected.
|
|
mBenchmarkBytesTotal = 0;
|
|
mBenchmarkBytesUnsent = 0;
|
|
|
|
otTcpCircularSendBufferForceDiscardAll(&mSendBuffer);
|
|
}
|
|
|
|
otTcpIncomingConnectionAction TcpExample::HandleTcpAcceptReady(otTcpListener *aListener,
|
|
const otSockAddr *aPeer,
|
|
otTcpEndpoint **aAcceptInto)
|
|
{
|
|
otTcpIncomingConnectionAction action;
|
|
|
|
OT_UNUSED_VARIABLE(aListener);
|
|
|
|
if (mEndpointConnected)
|
|
{
|
|
OutputFormat("TCP: Ignoring incoming connection request from ");
|
|
OutputSockAddr(*aPeer);
|
|
OutputLine(" (active socket is busy)");
|
|
|
|
ExitNow(action = OT_TCP_INCOMING_CONNECTION_ACTION_DEFER);
|
|
}
|
|
|
|
*aAcceptInto = &mEndpoint;
|
|
action = OT_TCP_INCOMING_CONNECTION_ACTION_ACCEPT;
|
|
|
|
#if OPENTHREAD_CONFIG_TLS_ENABLE
|
|
/*
|
|
* Natural to wait until the AcceptDone callback but with TFO we could get data before that
|
|
* so it doesn't make sense to wait until then.
|
|
*/
|
|
if (mUseTls)
|
|
{
|
|
int rv;
|
|
|
|
rv = mbedtls_ssl_config_defaults(&mSslConfig, MBEDTLS_SSL_IS_SERVER, MBEDTLS_SSL_TRANSPORT_STREAM,
|
|
MBEDTLS_SSL_PRESET_DEFAULT);
|
|
if (rv != 0)
|
|
{
|
|
OutputLine("mbedtls_ssl_config_defaults returned %d", rv);
|
|
}
|
|
|
|
mbedtls_ssl_conf_ca_chain(&mSslConfig, mSrvCert.next, nullptr);
|
|
rv = mbedtls_ssl_conf_own_cert(&mSslConfig, &mSrvCert, &mPKey);
|
|
|
|
if (rv != 0)
|
|
{
|
|
OutputLine("mbedtls_ssl_conf_own_cert returned %d", rv);
|
|
}
|
|
}
|
|
#endif // OPENTHREAD_CONFIG_TLS_ENABLE
|
|
|
|
exit:
|
|
return action;
|
|
}
|
|
|
|
void TcpExample::HandleTcpAcceptDone(otTcpListener *aListener, otTcpEndpoint *aEndpoint, const otSockAddr *aPeer)
|
|
{
|
|
OT_UNUSED_VARIABLE(aListener);
|
|
OT_UNUSED_VARIABLE(aEndpoint);
|
|
|
|
mEndpointConnected = true;
|
|
OutputFormat("Accepted connection from ");
|
|
OutputSockAddrLine(*aPeer);
|
|
}
|
|
|
|
otError TcpExample::ContinueBenchmarkCircularSend(void)
|
|
{
|
|
otError error = OT_ERROR_NONE;
|
|
size_t freeSpace;
|
|
|
|
while (mBenchmarkBytesUnsent != 0 && (freeSpace = otTcpCircularSendBufferGetFreeSpace(&mSendBuffer)) != 0)
|
|
{
|
|
size_t toSendThisIteration = OT_MIN(mBenchmarkBytesUnsent, sBenchmarkDataLength);
|
|
uint32_t flag = (toSendThisIteration < freeSpace && toSendThisIteration < mBenchmarkBytesUnsent)
|
|
? OT_TCP_CIRCULAR_SEND_BUFFER_WRITE_MORE_TO_COME
|
|
: 0;
|
|
size_t written = 0;
|
|
|
|
#if OPENTHREAD_CONFIG_TLS_ENABLE
|
|
if (mUseTls)
|
|
{
|
|
int rv = mbedtls_ssl_write(&mSslContext, reinterpret_cast<const unsigned char *>(sBenchmarkData),
|
|
toSendThisIteration);
|
|
|
|
if (rv > 0)
|
|
{
|
|
written = static_cast<size_t>(rv);
|
|
OT_ASSERT(written <= mBenchmarkBytesUnsent);
|
|
}
|
|
else if (rv != MBEDTLS_ERR_SSL_WANT_WRITE && rv != MBEDTLS_ERR_SSL_WANT_READ)
|
|
{
|
|
ExitNow(error = OT_ERROR_FAILED);
|
|
}
|
|
|
|
error = OT_ERROR_NONE;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
SuccessOrExit(error = otTcpCircularSendBufferWrite(&mEndpoint, &mSendBuffer, sBenchmarkData,
|
|
toSendThisIteration, &written, flag));
|
|
}
|
|
mBenchmarkBytesUnsent -= written;
|
|
}
|
|
|
|
exit:
|
|
if (error != OT_ERROR_NONE)
|
|
{
|
|
OutputLine("TCP Benchmark Failed");
|
|
mBenchmarkBytesTotal = 0;
|
|
mBenchmarkBytesUnsent = 0;
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
void TcpExample::OutputBenchmarkResult(void)
|
|
{
|
|
uint32_t thousandTimesGoodput =
|
|
(1000 * (mBenchmarkLastBytesTotal << 3) + (mBenchmarkTimeUsed >> 1)) / mBenchmarkTimeUsed;
|
|
|
|
OutputLine("TCP Benchmark Complete: Transferred %lu bytes in %lu milliseconds", ToUlong(mBenchmarkLastBytesTotal),
|
|
ToUlong(mBenchmarkTimeUsed));
|
|
OutputLine("TCP Goodput: %lu.%03u kb/s", ToUlong(thousandTimesGoodput / 1000),
|
|
static_cast<uint16_t>(thousandTimesGoodput % 1000));
|
|
}
|
|
|
|
void TcpExample::CompleteBenchmark(void)
|
|
{
|
|
mBenchmarkTimeUsed = TimerMilli::GetNow() - mBenchmarkStart;
|
|
mBenchmarkLastBytesTotal = mBenchmarkBytesTotal;
|
|
|
|
OutputBenchmarkResult();
|
|
|
|
mBenchmarkBytesTotal = 0;
|
|
}
|
|
|
|
#if OPENTHREAD_CONFIG_TLS_ENABLE
|
|
void TcpExample::PrepareTlsHandshake(void)
|
|
{
|
|
int rv;
|
|
|
|
rv = mbedtls_ssl_set_hostname(&mSslContext, "localhost");
|
|
|
|
if (rv != 0)
|
|
{
|
|
OutputLine("mbedtls_ssl_set_hostname returned %d", rv);
|
|
}
|
|
|
|
rv = mbedtls_ssl_set_hs_ecjpake_password(&mSslContext, reinterpret_cast<const unsigned char *>(sEcjpakePassword),
|
|
sEcjpakePasswordLength);
|
|
if (rv != 0)
|
|
{
|
|
OutputLine("mbedtls_ssl_set_hs_ecjpake_password returned %d", rv);
|
|
}
|
|
|
|
mbedtls_ssl_set_bio(&mSslContext, &mEndpointAndCircularSendBuffer, otTcpMbedTlsSslSendCallback,
|
|
otTcpMbedTlsSslRecvCallback, nullptr);
|
|
mTlsHandshakeComplete = false;
|
|
}
|
|
|
|
bool TcpExample::ContinueTlsHandshake(void)
|
|
{
|
|
bool wasNotAlreadyDone = false;
|
|
int rv;
|
|
|
|
if (!mTlsHandshakeComplete)
|
|
{
|
|
rv = mbedtls_ssl_handshake(&mSslContext);
|
|
|
|
if (rv == 0)
|
|
{
|
|
OutputLine("TLS Handshake Complete");
|
|
mTlsHandshakeComplete = true;
|
|
}
|
|
else if (rv != MBEDTLS_ERR_SSL_WANT_READ && rv != MBEDTLS_ERR_SSL_WANT_WRITE)
|
|
{
|
|
OutputLine("TLS Handshake Failed: %d", rv);
|
|
}
|
|
|
|
wasNotAlreadyDone = true;
|
|
}
|
|
|
|
return wasNotAlreadyDone;
|
|
}
|
|
#endif // OPENTHREAD_CONFIG_TLS_ENABLE
|
|
|
|
} // namespace Cli
|
|
} // namespace ot
|
|
|
|
#endif // OPENTHREAD_CONFIG_TCP_ENABLE && OPENTHREAD_CONFIG_CLI_TCP_ENABLE
|