| /* |
| * |
| * 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. |
| */ |
| |
| /** |
| * This file implements Inet::TCPEndPoint using LwIP. |
| */ |
| |
| #include <inet/TCPEndPointImplLwIP.h> |
| |
| #include <inet/InetFaultInjection.h> |
| #include <inet/arpa-inet-compatibility.h> |
| |
| #include <lib/support/CodeUtils.h> |
| #include <lib/support/SafeInt.h> |
| #include <lib/support/logging/CHIPLogging.h> |
| #include <system/SystemFaultInjection.h> |
| |
| #include <stdio.h> |
| #include <string.h> |
| #include <utility> |
| |
| #include <lwip/tcp.h> |
| #include <lwip/tcpip.h> |
| |
| static_assert(LWIP_VERSION_MAJOR > 1, "CHIP requires LwIP 2.0 or later"); |
| |
| // TODO: Update to use RunOnTCPIP. |
| static_assert(LWIP_TCPIP_CORE_LOCKING, "CHIP requires config LWIP_TCPIP_CORE_LOCKING enabled"); |
| |
| namespace chip { |
| namespace Inet { |
| |
| namespace { |
| |
| /* |
| * This logic to register a null operation callback with the LwIP TCP/IP task |
| * ensures that the TCP timer loop is started when a connection is established, |
| * which is necessary to ensure that initial SYN and SYN-ACK packets are |
| * retransmitted during the 3-way handshake. |
| */ |
| |
| void nil_tcpip_callback(void * _aContext) {} |
| |
| err_t start_tcp_timers(void) |
| { |
| return tcpip_callback(nil_tcpip_callback, NULL); |
| } |
| |
| } // anonymous namespace |
| |
| CHIP_ERROR TCPEndPointImplLwIP::BindImpl(IPAddressType addrType, const IPAddress & addr, uint16_t port, bool reuseAddr) |
| { |
| // Lock LwIP stack |
| LOCK_TCPIP_CORE(); |
| |
| // Get the appropriate type of PCB. |
| CHIP_ERROR res = GetPCB(addrType); |
| |
| // Bind the PCB to the specified address/port. |
| ip_addr_t ipAddr; |
| if (res == CHIP_NO_ERROR) |
| { |
| if (reuseAddr) |
| { |
| ip_set_option(mTCP, SOF_REUSEADDR); |
| } |
| res = addr.ToLwIPAddr(addrType, ipAddr); |
| } |
| |
| if (res == CHIP_NO_ERROR) |
| { |
| res = chip::System::MapErrorLwIP(tcp_bind(mTCP, &ipAddr, port)); |
| } |
| |
| // Unlock LwIP stack |
| UNLOCK_TCPIP_CORE(); |
| return res; |
| } |
| |
| CHIP_ERROR TCPEndPointImplLwIP::ListenImpl(uint16_t backlog) |
| { |
| // Start listening for incoming connections. |
| mTCP = tcp_listen(mTCP); |
| mLwIPEndPointType = LwIPEndPointType::TCP; |
| |
| tcp_arg(mTCP, this); |
| |
| tcp_accept(mTCP, LwIPHandleIncomingConnection); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR TCPEndPointImplLwIP::ConnectImpl(const IPAddress & addr, uint16_t port, InterfaceId intfId) |
| { |
| CHIP_ERROR res = CHIP_NO_ERROR; |
| IPAddressType addrType = addr.Type(); |
| |
| // LwIP does not provides an API for initiating a TCP connection via a specific interface. |
| // As a work-around, if the destination is an IPv6 link-local address, we bind the PCB |
| // to the link local address associated with the source interface; however this is only |
| // viable if the endpoint hasn't already been bound. |
| if (intfId.IsPresent()) |
| { |
| IPAddress intfLLAddr; |
| |
| if (!addr.IsIPv6LinkLocal() || mState == State::kBound) |
| return CHIP_ERROR_NOT_IMPLEMENTED; |
| |
| res = intfId.GetLinkLocalAddr(&intfLLAddr); |
| if (res != CHIP_NO_ERROR) |
| return res; |
| |
| res = Bind(IPAddressType::kIPv6, intfLLAddr, 0, true); |
| if (res != CHIP_NO_ERROR) |
| return res; |
| } |
| |
| // Lock LwIP stack |
| LOCK_TCPIP_CORE(); |
| |
| res = GetPCB(addrType); |
| |
| if (res == CHIP_NO_ERROR) |
| { |
| tcp_arg(mTCP, this); |
| tcp_err(mTCP, LwIPHandleError); |
| |
| ip_addr_t lwipAddr = addr.ToLwIPAddr(); |
| res = chip::System::MapErrorLwIP(tcp_connect(mTCP, &lwipAddr, port, LwIPHandleConnectComplete)); |
| |
| // Ensure that TCP timers are started |
| if (res == CHIP_NO_ERROR) |
| { |
| err_t error = start_tcp_timers(); |
| if (error != ERR_OK) |
| { |
| res = chip::System::MapErrorLwIP(error); |
| } |
| } |
| |
| if (res == CHIP_NO_ERROR) |
| { |
| mState = State::kConnecting; |
| Retain(); |
| } |
| } |
| |
| // Unlock LwIP stack |
| UNLOCK_TCPIP_CORE(); |
| return res; |
| } |
| |
| CHIP_ERROR TCPEndPointImplLwIP::GetPeerInfo(IPAddress * retAddr, uint16_t * retPort) const |
| { |
| VerifyOrReturnError(IsConnected(), CHIP_ERROR_INCORRECT_STATE); |
| |
| // Lock LwIP stack |
| LOCK_TCPIP_CORE(); |
| |
| CHIP_ERROR res = CHIP_ERROR_CONNECTION_ABORTED; |
| if (mTCP != nullptr) |
| { |
| *retPort = mTCP->remote_port; |
| *retAddr = IPAddress(mTCP->remote_ip); |
| res = CHIP_NO_ERROR; |
| } |
| |
| // Unlock LwIP stack |
| UNLOCK_TCPIP_CORE(); |
| |
| return res; |
| } |
| |
| CHIP_ERROR TCPEndPointImplLwIP::GetLocalInfo(IPAddress * retAddr, uint16_t * retPort) const |
| { |
| VerifyOrReturnError(IsConnected(), CHIP_ERROR_INCORRECT_STATE); |
| |
| // Lock LwIP stack |
| LOCK_TCPIP_CORE(); |
| |
| CHIP_ERROR res = CHIP_ERROR_CONNECTION_ABORTED; |
| if (mTCP != nullptr) |
| { |
| *retPort = mTCP->local_port; |
| *retAddr = IPAddress(mTCP->local_ip); |
| res = CHIP_NO_ERROR; |
| } |
| |
| // Unlock LwIP stack |
| UNLOCK_TCPIP_CORE(); |
| |
| return res; |
| } |
| |
| CHIP_ERROR TCPEndPointImplLwIP::GetInterfaceId(InterfaceId * retInterface) |
| { |
| VerifyOrReturnError(IsConnected(), CHIP_ERROR_INCORRECT_STATE); |
| |
| // TODO: Does netif_get_by_index(mTCP->netif_idx) do the right thing? I |
| // can't quite tell whether LwIP supports a specific interface id for TCP at |
| // all. For now just claim no particular interface id. |
| *retInterface = InterfaceId::Null(); |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR TCPEndPointImplLwIP::SendQueuedImpl(bool queueWasEmpty) |
| { |
| #if INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT |
| if (!mUserTimeoutTimerRunning) |
| { |
| // Timer was not running before this send. So, start |
| // the timer. |
| StartTCPUserTimeoutTimer(); |
| } |
| #endif // INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR TCPEndPointImplLwIP::EnableNoDelay() |
| { |
| VerifyOrReturnError(IsConnected(), CHIP_ERROR_INCORRECT_STATE); |
| |
| // Lock LwIP stack |
| LOCK_TCPIP_CORE(); |
| |
| CHIP_ERROR res = CHIP_ERROR_CONNECTION_ABORTED; |
| if (mTCP != nullptr) |
| { |
| tcp_nagle_disable(mTCP); |
| res = CHIP_NO_ERROR; |
| } |
| |
| // Unlock LwIP stack |
| UNLOCK_TCPIP_CORE(); |
| |
| return res; |
| } |
| |
| CHIP_ERROR TCPEndPointImplLwIP::EnableKeepAlive(uint16_t interval, uint16_t timeoutCount) |
| { |
| VerifyOrReturnError(IsConnected(), CHIP_ERROR_INCORRECT_STATE); |
| CHIP_ERROR res = CHIP_ERROR_NOT_IMPLEMENTED; |
| |
| #if LWIP_TCP_KEEPALIVE |
| |
| // Lock LwIP stack |
| LOCK_TCPIP_CORE(); |
| |
| if (mTCP != NULL) |
| { |
| // Set the idle interval |
| mTCP->keep_idle = (uint32_t) interval * 1000; |
| |
| // Set the probe retransmission interval. |
| mTCP->keep_intvl = (uint32_t) interval * 1000; |
| |
| // Set the probe timeout count |
| mTCP->keep_cnt = timeoutCount; |
| |
| // Enable keepalives for the connection. |
| ip_set_option(mTCP, SOF_KEEPALIVE); |
| res = CHIP_NO_ERROR; |
| } |
| else |
| { |
| res = CHIP_ERROR_CONNECTION_ABORTED; |
| } |
| |
| // Unlock LwIP stack |
| UNLOCK_TCPIP_CORE(); |
| |
| #endif // LWIP_TCP_KEEPALIVE |
| |
| return res; |
| } |
| |
| CHIP_ERROR TCPEndPointImplLwIP::DisableKeepAlive() |
| { |
| VerifyOrReturnError(IsConnected(), CHIP_ERROR_INCORRECT_STATE); |
| CHIP_ERROR res = CHIP_ERROR_NOT_IMPLEMENTED; |
| |
| #if LWIP_TCP_KEEPALIVE |
| |
| // Lock LwIP stack |
| LOCK_TCPIP_CORE(); |
| |
| if (mTCP != NULL) |
| { |
| // Disable keepalives on the connection. |
| ip_reset_option(mTCP, SOF_KEEPALIVE); |
| res = CHIP_NO_ERROR; |
| } |
| else |
| { |
| res = CHIP_ERROR_CONNECTION_ABORTED; |
| } |
| |
| // Unlock LwIP stack |
| UNLOCK_TCPIP_CORE(); |
| |
| #endif // LWIP_TCP_KEEPALIVE |
| |
| return res; |
| } |
| |
| CHIP_ERROR TCPEndPointImplLwIP::SetUserTimeoutImpl(uint32_t userTimeoutMillis) |
| { |
| return CHIP_ERROR_NOT_IMPLEMENTED; |
| } |
| |
| CHIP_ERROR TCPEndPointImplLwIP::DriveSendingImpl() |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| // Lock LwIP stack |
| LOCK_TCPIP_CORE(); |
| |
| // If the connection hasn't been aborted ... |
| if (mTCP != NULL) |
| { |
| err_t lwipErr; |
| |
| // Determine the current send window size. This is the maximum amount we can write to the connection. |
| uint16_t sendWindowSize = tcp_sndbuf(mTCP); |
| |
| // If there's data to be sent and the send window is open... |
| bool canSend = (RemainingToSend() > 0 && sendWindowSize > 0); |
| if (canSend) |
| { |
| // Find first packet buffer with remaining data to send by skipping |
| // all sent but un-acked data. |
| TCPEndPointImplLwIP::BufferOffset startOfUnsent = FindStartOfUnsent(); |
| |
| // While there's data to be sent and a window to send it in... |
| do |
| { |
| VerifyOrDie(!startOfUnsent.buffer.IsNull()); |
| VerifyOrDie(CanCastTo<uint16_t>(startOfUnsent.buffer->DataLength())); |
| uint16_t bufDataLen = static_cast<uint16_t>(startOfUnsent.buffer->DataLength()); |
| |
| // Get a pointer to the start of unsent data within the first buffer on the unsent queue. |
| const uint8_t * sendData = startOfUnsent.buffer->Start() + startOfUnsent.offset; |
| |
| // Determine the amount of data to send from the current buffer. |
| uint16_t sendLen = static_cast<uint16_t>(bufDataLen - startOfUnsent.offset); |
| if (sendLen > sendWindowSize) |
| sendLen = sendWindowSize; |
| |
| // Call LwIP to queue the data to be sent, telling it if there's more data to come. |
| // Data is queued in-place as a reference within the source packet buffer. It is |
| // critical that the underlying packet buffer not be freed until the data |
| // is acknowledged, otherwise retransmissions could use an invalid |
| // backing. Using TCP_WRITE_FLAG_COPY would eliminate this requirement, but overall |
| // requires many more memory allocations which may be problematic when very |
| // memory-constrained or when using pool-based allocations. |
| lwipErr = tcp_write(mTCP, sendData, sendLen, (canSend) ? TCP_WRITE_FLAG_MORE : 0); |
| if (lwipErr != ERR_OK) |
| { |
| err = chip::System::MapErrorLwIP(lwipErr); |
| break; |
| } |
| // Start accounting for the data sent as yet-to-be-acked. |
| // This cast is safe, because mUnackedLength + sendLen <= bufDataLen, which fits in uint16_t. |
| mUnackedLength = static_cast<uint16_t>(mUnackedLength + sendLen); |
| |
| // Adjust the unsent data offset by the length of data that was written. |
| // If the entire buffer has been sent advance to the next one. |
| // This cast is safe, because startOfUnsent.offset + sendLen <= bufDataLen, which fits in uint16_t. |
| startOfUnsent.offset = static_cast<uint16_t>(startOfUnsent.offset + sendLen); |
| if (startOfUnsent.offset == bufDataLen) |
| { |
| startOfUnsent.buffer.Advance(); |
| startOfUnsent.offset = 0; |
| } |
| |
| // Adjust the remaining window size. |
| sendWindowSize = static_cast<uint16_t>(sendWindowSize - sendLen); |
| |
| // Determine if there's more data to be sent after this buffer. |
| canSend = (RemainingToSend() > 0 && sendWindowSize > 0); |
| } while (canSend); |
| |
| // Call LwIP to send the queued data. |
| INET_FAULT_INJECT(FaultInjection::kFault_Send, err = chip::System::MapErrorLwIP(ERR_RTE)); |
| |
| if (err == CHIP_NO_ERROR) |
| { |
| lwipErr = tcp_output(mTCP); |
| |
| if (lwipErr != ERR_OK) |
| err = chip::System::MapErrorLwIP(lwipErr); |
| } |
| } |
| |
| if (err == CHIP_NO_ERROR) |
| { |
| // If in the SendShutdown state and the unsent queue is now empty, shutdown the PCB for sending. |
| if (mState == State::kSendShutdown && (RemainingToSend() == 0)) |
| { |
| lwipErr = tcp_shutdown(mTCP, 0, 1); |
| if (lwipErr != ERR_OK) |
| err = chip::System::MapErrorLwIP(lwipErr); |
| } |
| } |
| } |
| |
| else |
| err = CHIP_ERROR_CONNECTION_ABORTED; |
| |
| // Unlock LwIP stack |
| UNLOCK_TCPIP_CORE(); |
| |
| return err; |
| } |
| |
| void TCPEndPointImplLwIP::HandleConnectCompleteImpl() {} |
| |
| void TCPEndPointImplLwIP::DoCloseImpl(CHIP_ERROR err, State oldState) |
| { |
| // Lock LwIP stack |
| LOCK_TCPIP_CORE(); |
| |
| // If the LwIP PCB hasn't been closed yet... |
| if (mTCP != NULL) |
| { |
| // If the endpoint was a connection endpoint (vs. a listening endpoint)... |
| if (oldState != State::kListening) |
| { |
| // Prevent further callbacks for incoming data. This has the effect of instructing |
| // LwIP to discard any further data received from the peer. |
| tcp_recv(mTCP, NULL); |
| |
| // If entering the Closed state... |
| if (mState == State::kClosed) |
| { |
| // Prevent further callbacks to the error handler. |
| // |
| // Note: It is important to understand that LwIP can continue to make callbacks after |
| // a PCB has been closed via the tcp_close() API. In particular, LwIP will continue |
| // to call the 'data sent' callback to signal the acknowledgment of data that was |
| // sent, but not acknowledged, prior to the close call. Additionally, LwIP will call |
| // the error callback if the peer fails to respond in a timely manner to the either |
| // sent data or the FIN. Unfortunately, there is no callback in the case where the |
| // connection closes successfully. Because of this, it is impossible know definitively |
| // when LwIP will no longer make callbacks to its user. Thus we must block further |
| // callbacks to prevent them from happening after the endpoint has been freed. |
| // |
| tcp_err(mTCP, NULL); |
| |
| // If the endpoint is being closed without error, THEN call tcp_close() to close the underlying |
| // TCP connection gracefully, preserving any in-transit send data. |
| if (err == CHIP_NO_ERROR) |
| { |
| tcp_close(mTCP); |
| } |
| |
| // OTHERWISE, call tcp_abort() to abort the TCP connection, discarding any in-transit data. |
| else |
| { |
| tcp_abort(mTCP); |
| } |
| |
| // Discard the reference to the PCB to ensure there is no further interaction with it |
| // after this point. |
| mTCP = NULL; |
| mLwIPEndPointType = LwIPEndPointType::Unknown; |
| } |
| } |
| |
| // OTHERWISE the endpoint was being used for listening, so simply close it. |
| else |
| { |
| tcp_close(mTCP); |
| |
| // Discard the reference to the PCB to ensure there is no further interaction with it |
| // after this point. |
| mTCP = NULL; |
| mLwIPEndPointType = LwIPEndPointType::Unknown; |
| } |
| } |
| |
| // Unlock LwIP stack |
| UNLOCK_TCPIP_CORE(); |
| |
| if (mState == State::kClosed) |
| { |
| mUnackedLength = 0; |
| } |
| } |
| |
| CHIP_ERROR TCPEndPointImplLwIP::AckReceive(size_t len) |
| { |
| VerifyOrReturnError(IsConnected(), CHIP_ERROR_INCORRECT_STATE); |
| CHIP_ERROR res = CHIP_NO_ERROR; |
| |
| VerifyOrReturnError(CanCastTo<uint16_t>(len), CHIP_ERROR_INVALID_ARGUMENT); |
| |
| // Lock LwIP stack |
| LOCK_TCPIP_CORE(); |
| |
| if (mTCP != nullptr) |
| tcp_recved(mTCP, static_cast<uint16_t>(len)); |
| else |
| res = CHIP_ERROR_CONNECTION_ABORTED; |
| |
| // Unlock LwIP stack |
| UNLOCK_TCPIP_CORE(); |
| |
| return res; |
| } |
| |
| #if INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT |
| void TCPEndPointImplLwIP::TCPUserTimeoutHandler() |
| { |
| // Set the timer running flag to false |
| mUserTimeoutTimerRunning = false; |
| |
| // Close Connection as we have timed out and there is still |
| // data not sent out successfully. |
| DoClose(INET_ERROR_TCP_USER_TIMEOUT, false); |
| } |
| #endif // INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT |
| |
| uint16_t TCPEndPointImplLwIP::RemainingToSend() |
| { |
| if (mSendQueue.IsNull()) |
| { |
| return 0; |
| } |
| else |
| { |
| // We can never have reported more unacked data than there is pending |
| // in the send queue! This would indicate a critical accounting bug. |
| VerifyOrDie(mUnackedLength <= mSendQueue->TotalLength()); |
| |
| return static_cast<uint16_t>(mSendQueue->TotalLength() - mUnackedLength); |
| } |
| } |
| |
| TCPEndPointImplLwIP::BufferOffset TCPEndPointImplLwIP::FindStartOfUnsent() |
| { |
| // Find first packet buffer with remaining data to send by skipping |
| // all sent but un-acked data. This is necessary because of the Consume() |
| // call in HandleDataSent(), which potentially releases backing memory for |
| // fully-sent packet buffers, causing an invalidation of all possible |
| // offsets one might have cached. The TCP acnowledgements may come back |
| // with a variety of sizes depending on prior activity, and size of the |
| // send window. The only way to ensure we get the correct offsets into |
| // unsent data while retaining the buffers that have un-acked data is to |
| // traverse all sent-but-unacked data in the chain to reach the beginning |
| // of ready-to-send data. |
| TCPEndPointImplLwIP::BufferOffset startOfUnsent(mSendQueue.Retain()); |
| uint16_t leftToSkip = mUnackedLength; |
| |
| VerifyOrDie(leftToSkip < mSendQueue->TotalLength()); |
| |
| while (leftToSkip > 0) |
| { |
| VerifyOrDie(!startOfUnsent.buffer.IsNull()); |
| VerifyOrDie(CanCastTo<uint16_t>(startOfUnsent.buffer->DataLength())); |
| uint16_t bufDataLen = static_cast<uint16_t>(startOfUnsent.buffer->DataLength()); |
| if (leftToSkip >= bufDataLen) |
| { |
| // We have more to skip than current packet buffer size. |
| // Follow the chain to continue. |
| startOfUnsent.buffer.Advance(); |
| leftToSkip = static_cast<uint16_t>(leftToSkip - bufDataLen); |
| } |
| else |
| { |
| // Done skipping all data, currentUnsentBuf is first packet buffer |
| // containing unsent data. |
| startOfUnsent.offset = leftToSkip; |
| leftToSkip = 0; |
| } |
| } |
| |
| return startOfUnsent; |
| } |
| |
| CHIP_ERROR TCPEndPointImplLwIP::GetPCB(IPAddressType addrType) |
| { |
| // IMMPORTANT: This method MUST be called with the LwIP stack LOCKED! |
| if (mTCP == NULL) |
| { |
| switch (addrType) |
| { |
| case IPAddressType::kIPv6: |
| mTCP = tcp_new_ip_type(IPADDR_TYPE_V6); |
| break; |
| |
| #if INET_CONFIG_ENABLE_IPV4 |
| case IPAddressType::kIPv4: |
| mTCP = tcp_new_ip_type(IPADDR_TYPE_V4); |
| break; |
| #endif // INET_CONFIG_ENABLE_IPV4 |
| |
| default: |
| return INET_ERROR_WRONG_ADDRESS_TYPE; |
| } |
| |
| if (mTCP == NULL) |
| { |
| return CHIP_ERROR_NO_MEMORY; |
| } |
| else |
| { |
| mLwIPEndPointType = LwIPEndPointType::TCP; |
| } |
| } |
| else |
| { |
| switch (IP_GET_TYPE(&mTCP->local_ip)) |
| { |
| case IPADDR_TYPE_V6: |
| if (addrType != IPAddressType::kIPv6) |
| return INET_ERROR_WRONG_ADDRESS_TYPE; |
| break; |
| |
| #if INET_CONFIG_ENABLE_IPV4 |
| case IPADDR_TYPE_V4: |
| if (addrType != IPAddressType::kIPv4) |
| return INET_ERROR_WRONG_ADDRESS_TYPE; |
| break; |
| #endif // INET_CONFIG_ENABLE_IPV4 |
| |
| default: |
| break; |
| } |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| void TCPEndPointImplLwIP::HandleDataSent(uint16_t lenSent) |
| { |
| if (IsConnected()) |
| { |
| // Ensure we do not have internal inconsistency in the lwIP, which |
| // could cause invalid pointer accesses. |
| if (lenSent > mUnackedLength) |
| { |
| ChipLogError(Inet, "Got more ACKed bytes (%d) than were pending (%d)", (int) lenSent, (int) mUnackedLength); |
| DoClose(CHIP_ERROR_UNEXPECTED_EVENT, false); |
| return; |
| } |
| else if (mSendQueue.IsNull()) |
| { |
| ChipLogError(Inet, "Got ACK for %d bytes but data backing gone", (int) lenSent); |
| DoClose(CHIP_ERROR_UNEXPECTED_EVENT, false); |
| return; |
| } |
| |
| // Consume data off the head of the send queue equal to the amount of data being acknowledged. |
| mSendQueue.Consume(lenSent); |
| mUnackedLength = static_cast<uint16_t>(mUnackedLength - lenSent); |
| |
| #if INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT |
| // Only change the UserTimeout timer if lenSent > 0, |
| // indicating progress being made in sending data |
| // across. |
| if (lenSent > 0) |
| { |
| if (RemainingToSend() == 0) |
| { |
| // If the output queue has been flushed then stop the timer. |
| StopTCPUserTimeoutTimer(); |
| } |
| else |
| { |
| // Progress is being made. So, shift the timer |
| // forward if it was started. |
| RestartTCPUserTimeoutTimer(); |
| } |
| } |
| #endif // INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT |
| |
| // Mark the connection as being active. |
| MarkActive(); |
| |
| // If requested, call the app's OnDataSent callback. |
| if (OnDataSent != NULL) |
| OnDataSent(this, lenSent); |
| |
| // If unsent data exists, attempt to send it now... |
| if (RemainingToSend() > 0) |
| DriveSending(); |
| |
| // If in the closing state and the send queue is now empty, attempt to transition to closed. |
| if ((mState == State::kClosing) && (RemainingToSend() == 0)) |
| DoClose(CHIP_NO_ERROR, false); |
| } |
| } |
| |
| void TCPEndPointImplLwIP::HandleDataReceived(System::PacketBufferHandle && buf) |
| { |
| // Only receive new data while in the Connected or SendShutdown states. |
| if (mState == State::kConnected || mState == State::kSendShutdown) |
| { |
| // Mark the connection as being active. |
| MarkActive(); |
| |
| // If we received a data buffer, queue it on the receive queue. If there's already data in |
| // the queue, compact the data into the head buffer. |
| if (!buf.IsNull()) |
| { |
| if (mRcvQueue.IsNull()) |
| { |
| mRcvQueue = std::move(buf); |
| } |
| else |
| { |
| mRcvQueue->AddToEnd(std::move(buf)); |
| mRcvQueue->CompactHead(); |
| } |
| } |
| |
| // Otherwise buf == NULL means the other side closed the connection, so ... |
| else |
| { |
| |
| // If in the Connected state and the app has provided an OnPeerClose callback, |
| // enter the ReceiveShutdown state. Providing an OnPeerClose callback allows |
| // the app to decide whether to keep the send side of the connection open after |
| // the peer has closed. If no OnPeerClose is provided, we assume that the app |
| // wants to close both directions and automatically enter the Closing state. |
| if (mState == State::kConnected && OnPeerClose != NULL) |
| mState = State::kReceiveShutdown; |
| else |
| mState = State::kClosing; |
| |
| // Call the app's OnPeerClose. |
| if (OnPeerClose != NULL) |
| OnPeerClose(this); |
| } |
| |
| // Drive the received data into the app. |
| DriveReceiving(); |
| } |
| } |
| |
| void TCPEndPointImplLwIP::HandleIncomingConnection(TCPEndPoint * conEP) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| IPAddress peerAddr; |
| uint16_t peerPort; |
| |
| if (mState == State::kListening) |
| { |
| // If there's no callback available, fail with an error. |
| if (OnConnectionReceived == NULL) |
| err = CHIP_ERROR_NO_CONNECTION_HANDLER; |
| |
| // Extract the peer's address information. |
| if (err == CHIP_NO_ERROR) |
| err = conEP->GetPeerInfo(&peerAddr, &peerPort); |
| |
| // If successful, call the app's callback function. |
| if (err == CHIP_NO_ERROR) |
| OnConnectionReceived(this, conEP, peerAddr, peerPort); |
| |
| // Otherwise clean up and call the app's error callback. |
| else if (OnAcceptError != NULL) |
| OnAcceptError(this, err); |
| } |
| else |
| err = CHIP_ERROR_INCORRECT_STATE; |
| |
| // If something failed above, abort and free the connection end point. |
| if (err != CHIP_NO_ERROR) |
| conEP->Free(); |
| } |
| |
| void TCPEndPointImplLwIP::HandleError(CHIP_ERROR err) |
| { |
| if (mState == State::kListening) |
| { |
| if (OnAcceptError != NULL) |
| OnAcceptError(this, err); |
| } |
| else |
| DoClose(err, false); |
| } |
| |
| err_t TCPEndPointImplLwIP::LwIPHandleConnectComplete(void * arg, struct tcp_pcb * tpcb, err_t lwipErr) |
| { |
| err_t res = ERR_OK; |
| |
| if (arg != NULL) |
| { |
| TCPEndPointImplLwIP * ep = static_cast<TCPEndPointImplLwIP *>(arg); |
| |
| if (lwipErr == ERR_OK) |
| { |
| // Setup LwIP callback functions for data transmission. |
| tcp_recv(ep->mTCP, LwIPHandleDataReceived); |
| tcp_sent(ep->mTCP, LwIPHandleDataSent); |
| } |
| |
| // Post callback to HandleConnectComplete. |
| ep->Retain(); |
| CHIP_ERROR err = ep->GetSystemLayer().ScheduleLambda([ep, conErr = System::MapErrorLwIP(lwipErr)] { |
| ep->HandleConnectComplete(conErr); |
| ep->Release(); |
| }); |
| if (err != CHIP_NO_ERROR) |
| { |
| ep->Release(); |
| res = ERR_ABRT; |
| } |
| } |
| else |
| res = ERR_ABRT; |
| |
| if (res != ERR_OK) |
| tcp_abort(tpcb); |
| |
| return res; |
| } |
| |
| err_t TCPEndPointImplLwIP::LwIPHandleIncomingConnection(void * arg, struct tcp_pcb * tpcb, err_t lwipErr) |
| { |
| CHIP_ERROR err = chip::System::MapErrorLwIP(lwipErr); |
| |
| if (arg != NULL) |
| { |
| TCPEndPointImplLwIP * listenEP = static_cast<TCPEndPointImplLwIP *>(arg); |
| TCPEndPointImplLwIP * conEP = NULL; |
| System::Layer & lSystemLayer = listenEP->GetSystemLayer(); |
| |
| // Tell LwIP we've accepted the connection so it can decrement the listen PCB's pending_accepts counter. |
| tcp_accepted(listenEP->mTCP); |
| |
| // If we did in fact receive a connection, rather than an error, attempt to allocate an end point object. |
| // |
| // NOTE: Although most of the LwIP callbacks defer the real work to happen on the endpoint's thread |
| // (by posting events to the thread's event queue) we can't do that here because as soon as this |
| // function returns, LwIP is free to begin calling callbacks on the new PCB. For that to work we need |
| // to have an end point associated with the PCB. |
| // |
| if (err == CHIP_NO_ERROR) |
| { |
| TCPEndPoint * connectEndPoint = nullptr; |
| err = listenEP->GetEndPointManager().NewEndPoint(&connectEndPoint); |
| conEP = static_cast<TCPEndPointImplLwIP *>(connectEndPoint); |
| } |
| |
| // Ensure that TCP timers have been started |
| if (err == CHIP_NO_ERROR) |
| { |
| err_t error = start_tcp_timers(); |
| if (error != ERR_OK) |
| { |
| err = chip::System::MapErrorLwIP(error); |
| } |
| } |
| |
| // If successful in allocating an end point... |
| if (err == CHIP_NO_ERROR) |
| { |
| // Put the new end point into the Connected state. |
| conEP->mState = State::kConnected; |
| conEP->mTCP = tpcb; |
| conEP->mLwIPEndPointType = LwIPEndPointType::TCP; |
| conEP->Retain(); |
| |
| // Setup LwIP callback functions for the new PCB. |
| tcp_arg(tpcb, conEP); |
| tcp_recv(tpcb, LwIPHandleDataReceived); |
| tcp_sent(tpcb, LwIPHandleDataSent); |
| tcp_err(tpcb, LwIPHandleError); |
| |
| // Post a callback to the HandleConnectionReceived() function, passing it the new end point. |
| listenEP->Retain(); |
| conEP->Retain(); |
| err = lSystemLayer.ScheduleLambda([listenEP, conEP] { |
| listenEP->HandleIncomingConnection(conEP); |
| conEP->Release(); |
| listenEP->Release(); |
| }); |
| if (err != CHIP_NO_ERROR) |
| { |
| conEP->Release(); // for the Ref in ScheduleLambda |
| listenEP->Release(); |
| err = CHIP_ERROR_CONNECTION_ABORTED; |
| conEP->Release(); // for the Retain() above |
| conEP->Release(); // for the implied Retain() on construction |
| } |
| } |
| |
| // Otherwise, there was an error accepting the connection, so post a callback to the HandleError function. |
| else |
| { |
| listenEP->Retain(); |
| err = lSystemLayer.ScheduleLambda([listenEP, err] { |
| listenEP->HandleError(err); |
| listenEP->Release(); |
| }); |
| if (err != CHIP_NO_ERROR) |
| { |
| listenEP->Release(); |
| } |
| } |
| } |
| else |
| err = CHIP_ERROR_CONNECTION_ABORTED; |
| |
| if (err != CHIP_NO_ERROR && tpcb != NULL) |
| { |
| tcp_abort(tpcb); |
| return ERR_ABRT; |
| } |
| else |
| { |
| return ERR_OK; |
| } |
| } |
| |
| err_t TCPEndPointImplLwIP::LwIPHandleDataReceived(void * arg, struct tcp_pcb * tpcb, struct pbuf * p, err_t _err) |
| { |
| err_t res = ERR_OK; |
| |
| if (arg != NULL) |
| { |
| TCPEndPointImplLwIP * ep = static_cast<TCPEndPointImplLwIP *>(arg); |
| |
| // Post callback to HandleDataReceived. |
| ep->Retain(); |
| CHIP_ERROR err = ep->GetSystemLayer().ScheduleLambda([ep, p] { |
| ep->HandleDataReceived(System::PacketBufferHandle::Adopt(p)); |
| ep->Release(); |
| }); |
| if (err != CHIP_NO_ERROR) |
| { |
| ep->Release(); |
| res = ERR_ABRT; |
| } |
| } |
| else |
| res = ERR_ABRT; |
| |
| if (res != ERR_OK) |
| { |
| if (p != nullptr) |
| { |
| pbuf_free(p); |
| } |
| tcp_abort(tpcb); |
| } |
| |
| return res; |
| } |
| |
| err_t TCPEndPointImplLwIP::LwIPHandleDataSent(void * arg, struct tcp_pcb * tpcb, u16_t len) |
| { |
| err_t res = ERR_OK; |
| |
| if (arg != NULL) |
| { |
| TCPEndPointImplLwIP * ep = static_cast<TCPEndPointImplLwIP *>(arg); |
| |
| // Post callback to HandleDataReceived. |
| ep->Retain(); |
| CHIP_ERROR err = ep->GetSystemLayer().ScheduleLambda([ep, len] { |
| ep->HandleDataSent(len); |
| ep->Release(); |
| }); |
| if (err != CHIP_NO_ERROR) |
| { |
| ep->Release(); |
| res = ERR_ABRT; |
| } |
| } |
| else |
| res = ERR_ABRT; |
| |
| if (res != ERR_OK) |
| tcp_abort(tpcb); |
| |
| return res; |
| } |
| |
| void TCPEndPointImplLwIP::LwIPHandleError(void * arg, err_t lwipErr) |
| { |
| if (arg != NULL) |
| { |
| TCPEndPointImplLwIP * ep = static_cast<TCPEndPointImplLwIP *>(arg); |
| System::LayerFreeRTOS & lSystemLayer = static_cast<System::LayerFreeRTOS &>(ep->GetSystemLayer()); |
| |
| // At this point LwIP has already freed the PCB. Since the thread that owns the TCPEndPoint may |
| // try to use the PCB before it receives the TCPError event posted below, we set the PCB to NULL |
| // as a means to signal the other thread that the connection has been aborted. The implication |
| // of this is that the mTCP field is shared state between the two threads and thus must only be |
| // accessed with the LwIP lock held. |
| ep->mTCP = NULL; |
| ep->mLwIPEndPointType = LwIPEndPointType::Unknown; |
| |
| // Post callback to HandleError. |
| ep->Retain(); |
| CHIP_ERROR err = lSystemLayer.ScheduleLambda([ep, conErr = System::MapErrorLwIP(lwipErr)] { |
| ep->HandleError(conErr); |
| ep->Release(); |
| }); |
| if (err != CHIP_NO_ERROR) |
| ep->Release(); |
| } |
| } |
| |
| } // namespace Inet |
| } // namespace chip |