| /* |
| * 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(Output &aOutput) |
| : OutputWrapper(aOutput) |
| , mInitialized(false) |
| , mEndpointConnected(false) |
| , mSendBusy(false) |
| , mBenchmarkBytesTotal(0) |
| , mBenchmarkLinksLeft(0) |
| { |
| } |
| |
| otError TcpExample::ProcessHelp(Arg aArgs[]) |
| { |
| OT_UNUSED_VARIABLE(aArgs); |
| |
| for (const Command &command : sCommands) |
| { |
| 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(GetInstancePtr(), &mEndpoint, &endpointArgs)); |
| } |
| |
| { |
| otTcpListenerInitializeArgs listenerArgs; |
| |
| memset(&listenerArgs, 0x00, sizeof(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: |
| 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 = OT_ERROR_NONE; |
| 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 = BinarySearch::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); |
| 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) |
| { |
| 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; |
| |
| OutputLine("TCP Benchmark Complete: Transferred %u bytes in %u milliseconds", |
| static_cast<unsigned int>(mBenchmarkBytesTotal), static_cast<unsigned int>(milliseconds)); |
| 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) |
| { |
| 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) |
| { |
| OutputLine("TCP: Reached end of stream"); |
| } |
| } |
| |
| 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)); |
| |
| // 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 connection 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) |
| { |
| OutputFormat("TCP: Ignoring incoming connection request from "); |
| OutputSockAddr(*aPeer); |
| OutputLine(" (active socket is busy)"); |
| |
| 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); |
| |
| OutputFormat("Accepted connection from "); |
| OutputSockAddrLine(*aPeer); |
| } |
| |
| } // namespace Cli |
| } // namespace ot |
| |
| #endif // OPENTHREAD_CONFIG_TCP_ENABLE && OPENTHREAD_CONFIG_CLI_TCP_ENABLE |