/* * * 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 #include #include #include #include #include #include #include #include #include #include #include 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(startOfUnsent.buffer->DataLength())); uint16_t bufDataLen = static_cast(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(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(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(startOfUnsent.offset + sendLen); if (startOfUnsent.offset == bufDataLen) { startOfUnsent.buffer.Advance(); startOfUnsent.offset = 0; } // Adjust the remaining window size. sendWindowSize = static_cast(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(len), CHIP_ERROR_INVALID_ARGUMENT); // Lock LwIP stack LOCK_TCPIP_CORE(); if (mTCP != nullptr) tcp_recved(mTCP, static_cast(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(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(startOfUnsent.buffer->DataLength())); uint16_t bufDataLen = static_cast(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(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(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(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(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(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(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(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(arg); System::LayerFreeRTOS & lSystemLayer = static_cast(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