/* * * Copyright (c) 2020-2021 Project CHIP Authors * Copyright (c) 2013-2018 Nest Labs, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @file * This file implements the Inet::TCPEndPoint class, * where the CHIP Inet Layer encapsulates methods for interacting * with TCP transport endpoints (SOCK_DGRAM sockets on Linux and * BSD-derived systems) or LwIP TCP protocol control blocks, as * the system is configured accordingly. * */ #include #include #include #include #include #include #include #include #include #include namespace chip { namespace Inet { CHIP_ERROR TCPEndPoint::Bind(IPAddressType addrType, const IPAddress & addr, uint16_t port, bool reuseAddr) { VerifyOrReturnError(mState == State::kReady, CHIP_ERROR_INCORRECT_STATE); CHIP_ERROR res = CHIP_NO_ERROR; if (addr != IPAddress::Any && addr.Type() != IPAddressType::kAny && addr.Type() != addrType) { return INET_ERROR_WRONG_ADDRESS_TYPE; } res = BindImpl(addrType, addr, port, reuseAddr); if (res == CHIP_NO_ERROR) { mState = State::kBound; } return res; } CHIP_ERROR TCPEndPoint::Listen(uint16_t backlog) { VerifyOrReturnError(mState == State::kBound, CHIP_ERROR_INCORRECT_STATE); CHIP_ERROR res = CHIP_NO_ERROR; res = ListenImpl(backlog); if (res == CHIP_NO_ERROR) { // Once Listening, bump the reference count. The corresponding call to Release() will happen in DoClose(). Retain(); mState = State::kListening; } return res; } CHIP_ERROR TCPEndPoint::Connect(const IPAddress & addr, uint16_t port, InterfaceId intfId) { VerifyOrReturnError(mState == State::kReady || mState == State::kBound, CHIP_ERROR_INCORRECT_STATE); CHIP_ERROR res = CHIP_NO_ERROR; ReturnErrorOnFailure(ConnectImpl(addr, port, intfId)); StartConnectTimerIfSet(); return res; } CHIP_ERROR TCPEndPoint::Send(System::PacketBufferHandle && data, bool push) { VerifyOrReturnError(mState == State::kConnected || mState == State::kReceiveShutdown, CHIP_ERROR_INCORRECT_STATE); CHIP_ERROR res = CHIP_NO_ERROR; bool queueWasEmpty = mSendQueue.IsNull(); if (queueWasEmpty) { mSendQueue = std::move(data); } else { mSendQueue->AddToEnd(std::move(data)); } ReturnErrorOnFailure(SendQueuedImpl(queueWasEmpty)); if (push) { res = DriveSending(); } return res; } CHIP_ERROR TCPEndPoint::SetReceivedDataForTesting(System::PacketBufferHandle && data) { VerifyOrReturnError(IsConnected(), CHIP_ERROR_INCORRECT_STATE); mRcvQueue = std::move(data); return CHIP_NO_ERROR; } size_t TCPEndPoint::PendingSendLength() { if (!mSendQueue.IsNull()) { return mSendQueue->TotalLength(); } return 0; } size_t TCPEndPoint::PendingReceiveLength() { if (!mRcvQueue.IsNull()) { return mRcvQueue->TotalLength(); } return 0; } void TCPEndPoint::Shutdown() { VerifyOrReturn(IsConnected()); // If fully connected, enter the SendShutdown state. if (mState == State::kConnected) { mState = State::kSendShutdown; DriveSending(); } // Otherwise, if the peer has already closed their end of the connection, else if (mState == State::kReceiveShutdown) { DoClose(CHIP_NO_ERROR, false); } } void TCPEndPoint::Close() { // Clear the receive queue. mRcvQueue = nullptr; // Suppress closing callbacks, since the application explicitly called Close(). OnConnectionClosed = nullptr; OnPeerClose = nullptr; OnConnectComplete = nullptr; // Perform a graceful close. DoClose(CHIP_NO_ERROR, true); } void TCPEndPoint::Abort() { // Suppress closing callbacks, since the application explicitly called Abort(). OnConnectionClosed = nullptr; OnPeerClose = nullptr; OnConnectComplete = nullptr; DoClose(CHIP_ERROR_CONNECTION_ABORTED, true); } void TCPEndPoint::Free() { // Ensure no callbacks to the app after this point. OnAcceptError = nullptr; OnConnectComplete = nullptr; OnConnectionReceived = nullptr; OnConnectionClosed = nullptr; OnPeerClose = nullptr; OnDataReceived = nullptr; OnDataSent = nullptr; // Ensure the end point is Closed or Closing. Close(); // Release the Retain() that happened when the end point was allocated. Release(); } #if INET_TCP_IDLE_CHECK_INTERVAL > 0 void TCPEndPoint::SetIdleTimeout(uint32_t timeoutMS) { uint32_t newIdleTimeout = (timeoutMS + (INET_TCP_IDLE_CHECK_INTERVAL - 1)) / INET_TCP_IDLE_CHECK_INTERVAL; bool isIdleTimerRunning = IsIdleTimerRunning(GetEndPointManager()); if (newIdleTimeout > UINT16_MAX) { newIdleTimeout = UINT16_MAX; } mIdleTimeout = mRemainingIdleTime = static_cast(newIdleTimeout); if (!isIdleTimerRunning && mIdleTimeout) { GetSystemLayer().StartTimer(System::Clock::Milliseconds32(INET_TCP_IDLE_CHECK_INTERVAL), HandleIdleTimer, &GetEndPointManager()); } } // static void TCPEndPoint::HandleIdleTimer(chip::System::Layer * aSystemLayer, void * aAppState) { auto & endPointManager = *reinterpret_cast *>(aAppState); bool lTimerRequired = IsIdleTimerRunning(endPointManager); endPointManager.ForEachEndPoint([](TCPEndPoint * lEndPoint) -> Loop { if (!lEndPoint->IsConnected()) return Loop::Continue; if (lEndPoint->mIdleTimeout == 0) return Loop::Continue; if (lEndPoint->mRemainingIdleTime == 0) { lEndPoint->DoClose(INET_ERROR_IDLE_TIMEOUT, false); } else { --lEndPoint->mRemainingIdleTime; } return Loop::Continue; }); if (lTimerRequired) { aSystemLayer->StartTimer(System::Clock::Milliseconds32(INET_TCP_IDLE_CHECK_INTERVAL), HandleIdleTimer, &endPointManager); } } // static bool TCPEndPoint::IsIdleTimerRunning(EndPointManager & endPointManager) { // See if there are any TCP connections with the idle timer check in use. return Loop::Break == endPointManager.ForEachEndPoint([](TCPEndPoint * lEndPoint) { return (lEndPoint->mIdleTimeout == 0) ? Loop::Continue : Loop::Break; }); } #endif // INET_TCP_IDLE_CHECK_INTERVAL > 0 CHIP_ERROR TCPEndPoint::SetUserTimeout(uint32_t userTimeoutMillis) { VerifyOrReturnError(IsConnected(), CHIP_ERROR_INCORRECT_STATE); CHIP_ERROR res = CHIP_NO_ERROR; #if INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT // Store the User timeout configuration if it is being overridden. mUserTimeoutMillis = userTimeoutMillis; #else // !INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT res = SetUserTimeoutImpl(userTimeoutMillis); #endif // !INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT return res; } void TCPEndPoint::StartConnectTimerIfSet() { if (mConnectTimeoutMsecs > 0) { GetSystemLayer().StartTimer(System::Clock::Milliseconds32(mConnectTimeoutMsecs), TCPConnectTimeoutHandler, this); } } void TCPEndPoint::StopConnectTimer() { GetSystemLayer().CancelTimer(TCPConnectTimeoutHandler, this); } void TCPEndPoint::TCPConnectTimeoutHandler(chip::System::Layer * aSystemLayer, void * aAppState) { TCPEndPoint * tcpEndPoint = reinterpret_cast(aAppState); VerifyOrDie((aSystemLayer != nullptr) && (tcpEndPoint != nullptr)); // Close Connection as we have timed out and Connect has not returned to stop this timer. tcpEndPoint->DoClose(INET_ERROR_TCP_CONNECT_TIMEOUT, false); } bool TCPEndPoint::IsConnected(State state) { return state == State::kConnected || state == State::kSendShutdown || state == State::kReceiveShutdown || state == State::kClosing; } CHIP_ERROR TCPEndPoint::DriveSending() { CHIP_ERROR err = DriveSendingImpl(); if (err != CHIP_NO_ERROR) { DoClose(err, false); } CHIP_SYSTEM_FAULT_INJECT_ASYNC_EVENT(); return err; } void TCPEndPoint::DriveReceiving() { // If there's data in the receive queue and the app is ready to receive it then call the app's callback // with the entire receive queue. if (!mRcvQueue.IsNull() && mReceiveEnabled && OnDataReceived != nullptr) { // Acknowledgement is done after handling the buffers to allow the // application processing to throttle flow. size_t ackLength = mRcvQueue->TotalLength(); CHIP_ERROR err = OnDataReceived(this, std::move(mRcvQueue)); if (err != CHIP_NO_ERROR) { DoClose(err, false); return; } AckReceive(ackLength); } // If the connection is closing, and the receive queue is now empty, call DoClose() to complete // the process of closing the connection. if (mState == State::kClosing && mRcvQueue.IsNull()) { DoClose(CHIP_NO_ERROR, false); } } void TCPEndPoint::HandleConnectComplete(CHIP_ERROR err) { // If the connect succeeded enter the Connected state and call the app's callback. if (err == CHIP_NO_ERROR) { // Stop the TCP Connect timer in case it is still running. StopConnectTimer(); // Mark the connection as being active. MarkActive(); mState = State::kConnected; HandleConnectCompleteImpl(); if (OnConnectComplete != nullptr) { OnConnectComplete(this, CHIP_NO_ERROR); } } // Otherwise, close the connection with an error. else { DoClose(err, false); } } void TCPEndPoint::DoClose(CHIP_ERROR err, bool suppressCallback) { State oldState = mState; // If in one of the connected states (Connected, LocalShutdown, PeerShutdown or Closing) // AND this is a graceful close (i.e. not prompted by an error) // AND there is data waiting to be processed on either the send or receive queues // ... THEN enter the Closing state, allowing the queued data to drain, // ... OTHERWISE go straight to the Closed state. if (IsConnected() && err == CHIP_NO_ERROR && (!mSendQueue.IsNull() || !mRcvQueue.IsNull())) { mState = State::kClosing; } else { mState = State::kClosed; } if (oldState != State::kClosed) { // Stop the Connect timer in case it is still running. StopConnectTimer(); } // If not making a state transition, return immediately. if (mState == oldState) { return; } DoCloseImpl(err, oldState); #if INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT // Stop the TCP UserTimeout timer if it is running. StopTCPUserTimeoutTimer(); #endif // INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT // If entering the Closed state... if (mState == State::kClosed) { // Clear clear the send and receive queues. mSendQueue = nullptr; mRcvQueue = nullptr; // Call the appropriate app callback if allowed. if (!suppressCallback) { if (oldState == State::kConnecting) { if (OnConnectComplete != nullptr) { OnConnectComplete(this, err); } } else if ((oldState == State::kConnected || oldState == State::kSendShutdown || oldState == State::kReceiveShutdown || oldState == State::kClosing) && OnConnectionClosed != nullptr) { OnConnectionClosed(this, err); } } // Decrement the ref count that was added when the connection started (in Connect()) or listening started (in Listen()). if (oldState != State::kReady && oldState != State::kBound) { Release(); } } } #if INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT void TCPEndPoint::ScheduleNextTCPUserTimeoutPoll(uint32_t aTimeOut) { GetSystemLayer().StartTimer(System::Clock::Milliseconds32(aTimeOut), TCPUserTimeoutHandler, this); } void TCPEndPoint::StartTCPUserTimeoutTimer() { ScheduleNextTCPUserTimeoutPoll(mUserTimeoutMillis); mUserTimeoutTimerRunning = true; } void TCPEndPoint::StopTCPUserTimeoutTimer() { GetSystemLayer().CancelTimer(TCPUserTimeoutHandler, this); mUserTimeoutTimerRunning = false; } void TCPEndPoint::RestartTCPUserTimeoutTimer() { StopTCPUserTimeoutTimer(); StartTCPUserTimeoutTimer(); } // static void TCPEndPoint::TCPUserTimeoutHandler(chip::System::Layer * aSystemLayer, void * aAppState) { TCPEndPoint * tcpEndPoint = reinterpret_cast(aAppState); VerifyOrDie((aSystemLayer != nullptr) && (tcpEndPoint != nullptr)); tcpEndPoint->TCPUserTimeoutHandler(); } #endif // INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT } // namespace Inet } // namespace chip