/* * Copyright (c) 2021, The OpenThread Authors. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holder nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /** * @file * This file implements a TCP CLI tool. */ #include "openthread-core-config.h" #include "cli_config.h" #if OPENTHREAD_CONFIG_TCP_ENABLE && OPENTHREAD_CONFIG_CLI_TCP_ENABLE #include "cli_tcp.hpp" #include #include "cli/cli.hpp" #include "common/encoding.hpp" #include "common/timer.hpp" namespace ot { namespace Cli { constexpr TcpExample::Command TcpExample::sCommands[]; TcpExample::TcpExample(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(otTcpEndpointGetContext(aEndpoint))->HandleTcpEstablished(aEndpoint); } void TcpExample::HandleTcpSendDoneCallback(otTcpEndpoint *aEndpoint, otLinkedBuffer *aData) { static_cast(otTcpEndpointGetContext(aEndpoint))->HandleTcpSendDone(aEndpoint, aData); } void TcpExample::HandleTcpReceiveAvailableCallback(otTcpEndpoint *aEndpoint, size_t aBytesAvailable, bool aEndOfStream, size_t aBytesRemaining) { static_cast(otTcpEndpointGetContext(aEndpoint)) ->HandleTcpReceiveAvailable(aEndpoint, aBytesAvailable, aEndOfStream, aBytesRemaining); } void TcpExample::HandleTcpDisconnectedCallback(otTcpEndpoint *aEndpoint, otTcpDisconnectedReason aReason) { static_cast(otTcpEndpointGetContext(aEndpoint))->HandleTcpDisconnected(aEndpoint, aReason); } otTcpIncomingConnectionAction TcpExample::HandleTcpAcceptReadyCallback(otTcpListener * aListener, const otSockAddr *aPeer, otTcpEndpoint ** aAcceptInto) { return static_cast(otTcpListenerGetContext(aListener)) ->HandleTcpAcceptReady(aListener, aPeer, aAcceptInto); } void TcpExample::HandleTcpAcceptDoneCallback(otTcpListener * aListener, otTcpEndpoint * aEndpoint, const otSockAddr *aPeer) { static_cast(otTcpListenerGetContext(aListener))->HandleTcpAcceptDone(aListener, aEndpoint, aPeer); } void TcpExample::HandleTcpEstablished(otTcpEndpoint *aEndpoint) { OT_UNUSED_VARIABLE(aEndpoint); 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(mBenchmarkBytesTotal), static_cast(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(data->mLength), data->mLength, reinterpret_cast(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