/* * * Copyright (c) 2020-2021 Project CHIP Authors * Copyright (c) 2013-2017 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 header file defines 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. */ #pragma once #include #include #include #include #include #include #include namespace chip { namespace Inet { class TCPTest; /** * @brief Objects of this class represent TCP transport endpoints. * * @details * CHIP Inet Layer encapsulates methods for interacting with TCP transport * endpoints (SOCK_STREAM sockets on Linux and BSD-derived systems) or LwIP * TCP protocol control blocks, as the system is configured accordingly. */ class DLL_EXPORT TCPEndPoint : public EndPointBasis { public: /** * @brief Bind the endpoint to an interface IP address. * * @param[in] addrType the protocol version of the IP address * @param[in] addr the IP address (must be an interface address) * @param[in] port the TCP port * @param[in] reuseAddr option to share binding with other endpoints * * @retval CHIP_NO_ERROR success: endpoint bound to address * @retval CHIP_ERROR_INCORRECT_STATE endpoint has been bound previously * @retval CHIP_ERROR_NO_MEMORY insufficient memory for endpoint * * @retval INET_ERROR_WRONG_PROTOCOL_TYPE * \c addrType does not match \c IPVer. * * @retval INET_ERROR_WRONG_ADDRESS_TYPE * \c addrType is \c IPAddressType::kAny, or the type of \c addr is not * equal to \c addrType. * * @retval other another system or platform error * * @details * Binds the endpoint to the specified network interface IP address. * * On LwIP, this method must not be called with the LwIP stack lock * already acquired. */ CHIP_ERROR Bind(IPAddressType addrType, const IPAddress & addr, uint16_t port, bool reuseAddr = false); /** * @brief Prepare the endpoint to receive TCP messages. * * @param[in] backlog maximum depth of connection acceptance queue * * @retval CHIP_NO_ERROR success: endpoint ready to receive messages. * @retval CHIP_ERROR_INCORRECT_STATE endpoint is already listening. * * @details * If \c mState is already \c State::kListening, then no operation is * performed, otherwise the \c mState is set to \c State::kListening and * the endpoint is prepared to received TCP messages, according to the * semantics of the platform. * * On some platforms, the \c backlog argument is not used (the depth of * the queue is fixed; only one connection may be accepted at a time). * * On LwIP systems, this method must not be called with the LwIP stack * lock already acquired */ CHIP_ERROR Listen(uint16_t backlog); /** * @brief Initiate a TCP connection. * * @param[in] addr the destination IP address * @param[in] port the destination TCP port * @param[in] intfId an optional network interface indicator * * @retval CHIP_NO_ERROR success: \c msg is queued for transmit. * @retval CHIP_ERROR_NOT_IMPLEMENTED system implementation not complete. * * @retval INET_ERROR_WRONG_ADDRESS_TYPE * the destination address and the bound interface address do not * have matching protocol versions or address type, or the destination * address is an IPv6 link-local address and \c intfId is not specified. * * @retval other another system or platform error * * @details * If possible, then this method initiates a TCP connection to the * destination \c addr (with \c intfId used as the scope * identifier for IPv6 link-local destinations) and \c port. */ CHIP_ERROR Connect(const IPAddress & addr, uint16_t port, InterfaceId intfId = InterfaceId::Null()); /** * @brief Extract IP address and TCP port of remote endpoint. * * @param[out] retAddr IP address of remote endpoint. * @param[out] retPort TCP port of remote endpoint. * * @retval CHIP_NO_ERROR success: address and port extracted. * @retval CHIP_ERROR_INCORRECT_STATE TCP connection not established. * @retval CHIP_ERROR_CONNECTION_ABORTED TCP connection no longer open. * * @details * Do not use \c nullptr for either argument. */ virtual CHIP_ERROR GetPeerInfo(IPAddress * retAddr, uint16_t * retPort) const = 0; /** * @brief Extract IP address and TCP port of local endpoint. * * @param[out] retAddr IP address of local endpoint. * @param[out] retPort TCP port of local endpoint. * * @retval CHIP_NO_ERROR success: address and port extracted. * @retval CHIP_ERROR_INCORRECT_STATE TCP connection not established. * @retval CHIP_ERROR_CONNECTION_ABORTED TCP connection no longer open. * * @details * Do not use \c nullptr for either argument. */ virtual CHIP_ERROR GetLocalInfo(IPAddress * retAddr, uint16_t * retPort) const = 0; /** * @brief Extract the interface id of the TCP endpoint. * * @param[out] retInterface The interface id. * * @retval CHIP_NO_ERROR success: address and port extracted. * @retval CHIP_ERROR_INCORRECT_STATE TCP connection not established. * @retval CHIP_ERROR_CONNECTION_ABORTED TCP connection no longer open. */ virtual CHIP_ERROR GetInterfaceId(InterfaceId * retInterface) = 0; /** * @brief Send message text on TCP connection. * * @param[out] data Message text to send. * @param[out] push If \c true, then send immediately, otherwise queue. * * @retval CHIP_NO_ERROR success: address and port extracted. * @retval CHIP_ERROR_INCORRECT_STATE TCP connection not established. */ CHIP_ERROR Send(chip::System::PacketBufferHandle && data, bool push = true); /** * Disable reception. * * Disable all event handlers. Data sent to an endpoint that disables * reception will be acknowledged until the receive window is exhausted. */ void DisableReceive() { mReceiveEnabled = false; } /** * Enable reception. * * Enable all event handlers. Data sent to an endpoint that disables * reception will be acknowledged until the receive window is exhausted. */ void EnableReceive() { mReceiveEnabled = true; DriveReceiving(); } /** * Switch off Nagle buffering algorithm. */ virtual CHIP_ERROR EnableNoDelay() = 0; /** * @brief * Enable TCP keepalive probes on the associated TCP connection. * * @param[in] interval * The interval (in seconds) between keepalive probes. This value also controls * the time between last data packet sent and the transmission of the first keepalive * probe. * * @param[in] timeoutCount * The maximum number of unacknowledged probes before the connection will be deemed * to have failed. * * @retval CHIP_NO_ERROR success: address and port extracted. * @retval CHIP_ERROR_INCORRECT_STATE TCP connection not established. * @retval CHIP_ERROR_CONNECTION_ABORTED TCP connection no longer open. * @retval CHIP_ERROR_NOT_IMPLEMENTED system implementation not complete. * * @retval other another system or platform error * * @note * This method can only be called when the endpoint is in one of the connected states. * * This method can be called multiple times to adjust the keepalive interval or timeout * count. * * @details * Start automatically transmitting TCP "keep-alive" probe segments every * \c interval seconds. The connection will abort automatically after * receiving a negative response, or after sending \c timeoutCount * probe segments without receiving a positive response. * * See RFC 1122, section 4.2.3.6 for specification details. */ virtual CHIP_ERROR EnableKeepAlive(uint16_t interval, uint16_t timeoutCount) = 0; /** * @brief Disable the TCP "keep-alive" option. * * This method can only be called when the endpoint is in one of the connected states. * This method does nothing if keepalives have not been enabled on the endpoint. * * @retval CHIP_NO_ERROR success: address and port extracted. * @retval CHIP_ERROR_INCORRECT_STATE TCP connection not established. * @retval CHIP_ERROR_CONNECTION_ABORTED TCP connection no longer open. * @retval CHIP_ERROR_NOT_IMPLEMENTED system implementation not complete. * * @retval other another system or platform error */ virtual CHIP_ERROR DisableKeepAlive() = 0; /** * @brief Acknowledge receipt of message text. * * @param[in] len number of bytes to acknowledge. * * @retval CHIP_NO_ERROR success: reception acknowledged. * @retval CHIP_ERROR_INCORRECT_STATE TCP connection not established. * @retval CHIP_ERROR_CONNECTION_ABORTED TCP connection no longer open. * * @details * Use this method to acknowledge reception of all or part of the data * received. The operational semantics are undefined if \c len is larger * than the total outstanding unacknowledged received data. */ virtual CHIP_ERROR AckReceive(size_t len) = 0; /** * @brief Set the receive queue, for testing. * * @param[out] data Message text to push. * * @retval CHIP_NO_ERROR success: reception acknowledged. * @retval CHIP_ERROR_INCORRECT_STATE TCP connection not established. * * @details * This method may only be called by data reception event handlers to * put data on the receive queue for unit test purposes. */ CHIP_ERROR SetReceivedDataForTesting(chip::System::PacketBufferHandle && data); /** * @brief Extract the length of the data awaiting first transmit. * * @return Number of untransmitted bytes in the transmit queue. */ size_t PendingSendLength(); /** * @brief Extract the length of the unacknowledged receive data. * * @return Number of bytes in the receive queue that have not yet been * acknowledged with AckReceive(uint16_t len). */ size_t PendingReceiveLength(); /** * @brief Initiate TCP half close, in other words, finished with sending. */ void Shutdown(); /** * @brief Initiate TCP full close, in other words, finished with both send and * receive. */ void Close(); /** * @brief Abortively close the endpoint, in other words, send RST packets. */ void Abort(); /** * @brief Initiate (or continue) TCP full close, ignoring errors. * * @details * The object is returned to the free pool, and all remaining user * references are subsequently invalid. */ void Free(); /** * @brief Extract whether TCP connection is established. */ bool IsConnected() const { return IsConnected(mState); } /** * Set timeout for Connect to succeed or return an error. */ void SetConnectTimeout(const uint32_t connTimeoutMsecs) { mConnectTimeoutMsecs = connTimeoutMsecs; } #if INET_TCP_IDLE_CHECK_INTERVAL > 0 /** * @brief Set timer event for idle activity. * * @param[in] timeoutMS The timeout in milliseconds * * @details * Set the idle timer interval to \c timeoutMS milliseconds. A zero * time interval implies the idle timer is disabled. */ void SetIdleTimeout(uint32_t timeoutMS); #endif // INET_TCP_IDLE_CHECK_INTERVAL > 0 /** * @brief Note activity, in other words, reset the idle timer. * * @details * Reset the idle timer to zero. */ void MarkActive() { #if INET_TCP_IDLE_CHECK_INTERVAL > 0 mRemainingIdleTime = mIdleTimeout; #endif // INET_TCP_IDLE_CHECK_INTERVAL > 0 } /** * @brief Set the TCP TCP_USER_TIMEOUT socket option. * * @param[in] userTimeoutMillis Tcp user timeout value in milliseconds. * * @retval CHIP_NO_ERROR success: address and port extracted. * @retval CHIP_ERROR_NOT_IMPLEMENTED system implementation not complete. * * @retval other another system or platform error * * @details * When the value is greater than 0, it specifies the maximum amount of * time in milliseconds that transmitted data may remain * unacknowledged before TCP will forcibly close the * corresponding connection. If the option value is specified as 0, * TCP will to use the system default. * See RFC 5482, for further details. * * @note * This method can only be called when the endpoint is in one of the connected states. * * This method can be called multiple times to adjust the keepalive interval or timeout * count. */ CHIP_ERROR SetUserTimeout(uint32_t userTimeoutMillis); /** * @brief Type of connection establishment event handling function. * * @param[in] endPoint The TCP endpoint associated with the event. * @param[in] err \c CHIP_NO_ERROR if success, else another code. * * @details * Provide a function of this type to the \c OnConnectComplete delegate * member to process connection establishment events on \c endPoint. The * \c err argument distinguishes successful connections from failures. */ typedef void (*OnConnectCompleteFunct)(TCPEndPoint * endPoint, CHIP_ERROR err); /** * The endpoint's connection establishment event handling function * delegate. */ OnConnectCompleteFunct OnConnectComplete; /** * @brief Type of data reception event handling function. * * @param[in] endPoint The TCP endpoint associated with the event. * @param[in] data The data received. * * @retval CHIP_NO_ERROR If the received data can be handled by higher layers. * @retval other If the received data can not be used, and higher layers will not see it. * * @details * Provide a function of this type to the \c OnDataReceived delegate * member to process data reception events on \c endPoint where \c data * is the message text received. * * If this function returns an error, the connection will be closed, since higher layers * are not able to process the data for a better response. */ typedef CHIP_ERROR (*OnDataReceivedFunct)(TCPEndPoint * endPoint, chip::System::PacketBufferHandle && data); /** * The endpoint's message text reception event handling function delegate. */ OnDataReceivedFunct OnDataReceived; /** * @brief Type of data transmission event handling function. * * @param[in] endPoint The TCP endpoint associated with the event. * @param[in] len Number of bytes added to the transmit window. * * @details * Provide a function of this type to the \c OnDataSent delegate * member to process data transmission events on \c endPoint where \c len * is the length of the message text added to the TCP transmit window, * which are eligible for sending by the underlying network stack. */ typedef void (*OnDataSentFunct)(TCPEndPoint * endPoint, size_t len); /** * The endpoint's message text transmission event handling function * delegate. */ OnDataSentFunct OnDataSent; /** * @brief Type of connection establishment event handling function. * * @param[in] endPoint The TCP endpoint associated with the event. * @param[in] err \c CHIP_NO_ERROR if success, else another code. * * @details * Provide a function of this type to the \c OnConnectionClosed delegate * member to process connection termination events on \c endPoint. The * \c err argument distinguishes successful terminations from failures. */ typedef void (*OnConnectionClosedFunct)(TCPEndPoint * endPoint, CHIP_ERROR err); /** The endpoint's close event handling function delegate. */ OnConnectionClosedFunct OnConnectionClosed; /** * @brief Type of half-close reception event handling function. * * @param[in] endPoint The TCP endpoint associated with the event. * * @details * Provide a function of this type to the \c OnPeerClose delegate member * to process connection termination events on \c endPoint. */ typedef void (*OnPeerCloseFunct)(TCPEndPoint * endPoint); /** The endpoint's half-close receive event handling function delegate. */ OnPeerCloseFunct OnPeerClose; /** * @brief Type of connection received event handling function. * * @param[in] listeningEndPoint The listening TCP endpoint. * @param[in] conEndPoint The newly received TCP endpoint. * @param[in] peerAddr The IP address of the remote peer. * @param[in] peerPort The TCP port of the remote peer. * * @details * Provide a function of this type to the \c OnConnectionReceived delegate * member to process connection reception events on \c listeningEndPoint. * The newly received endpoint \c conEndPoint is located at IP address * \c peerAddr and TCP port \c peerPort. */ typedef void (*OnConnectionReceivedFunct)(TCPEndPoint * listeningEndPoint, TCPEndPoint * conEndPoint, const IPAddress & peerAddr, uint16_t peerPort); /** The endpoint's connection receive event handling function delegate. */ OnConnectionReceivedFunct OnConnectionReceived; /** * @brief Type of connection acceptance error event handling function. * * @param[in] endPoint The TCP endpoint associated with the event. * @param[in] err The reason for the error. * * @details * Provide a function of this type to the \c OnAcceptError delegate * member to process connection acceptance error events on \c endPoint. The * \c err argument provides specific detail about the type of the error. */ typedef void (*OnAcceptErrorFunct)(TCPEndPoint * endPoint, CHIP_ERROR err); /** * The endpoint's connection acceptance event handling function delegate. */ OnAcceptErrorFunct OnAcceptError; /** * Size of the largest TCP packet that can be received. */ constexpr static size_t kMaxReceiveMessageSize = System::PacketBuffer::kMaxAllocSize; protected: friend class TCPTest; TCPEndPoint(EndPointManager & endPointManager) : EndPointBasis(endPointManager), OnConnectComplete(nullptr), OnDataReceived(nullptr), OnDataSent(nullptr), OnConnectionClosed(nullptr), OnPeerClose(nullptr), OnConnectionReceived(nullptr), OnAcceptError(nullptr), mState(State::kReady), mReceiveEnabled(true), mConnectTimeoutMsecs(0) // Initialize to zero for using system defaults. #if INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT , mUserTimeoutMillis(INET_CONFIG_DEFAULT_TCP_USER_TIMEOUT_MSEC), mUserTimeoutTimerRunning(false) #endif // INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT {} virtual ~TCPEndPoint() = default; /** * Basic dynamic state of the underlying endpoint. * * Objects are initialized in the "ready" state, proceed to subsequent * states corresponding to a simplification of the states of the TCP * transport state machine. */ enum class State : uint8_t { kReady = 0, /**< Endpoint initialized, but not bound. */ kBound = 1, /**< Endpoint bound, but not listening. */ kListening = 2, /**< Endpoint receiving connections. */ kConnecting = 3, /**< Endpoint attempting to connect. */ kConnected = 4, /**< Endpoint connected, ready for tx/rx. */ kSendShutdown = 5, /**< Endpoint initiated its half-close. */ kReceiveShutdown = 6, /**< Endpoint responded to half-close. */ kClosing = 7, /**< Endpoint closing bidirectionally. */ kClosed = 8 /**< Endpoint closed, ready for release. */ } mState; /** Control switch indicating whether the application is receiving data. */ bool mReceiveEnabled; chip::System::PacketBufferHandle mRcvQueue; chip::System::PacketBufferHandle mSendQueue; #if INET_TCP_IDLE_CHECK_INTERVAL > 0 static void HandleIdleTimer(System::Layer * aSystemLayer, void * aAppState); static bool IsIdleTimerRunning(EndPointManager & endPointManager); uint16_t mIdleTimeout; // in units of INET_TCP_IDLE_CHECK_INTERVAL; zero means no timeout uint16_t mRemainingIdleTime; // in units of INET_TCP_IDLE_CHECK_INTERVAL #endif // INET_TCP_IDLE_CHECK_INTERVAL > 0 uint32_t mConnectTimeoutMsecs; // This is the timeout to wait for a Connect call to succeed or // return an error; zero means use system defaults. #if INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT uint32_t mUserTimeoutMillis; // The configured TCP user timeout value in milliseconds. // If 0, assume not set. bool mUserTimeoutTimerRunning; // Indicates whether the TCP UserTimeout timer has been started. static void TCPUserTimeoutHandler(chip::System::Layer * aSystemLayer, void * aAppState); virtual void TCPUserTimeoutHandler() = 0; void StartTCPUserTimeoutTimer(); void StopTCPUserTimeoutTimer(); void RestartTCPUserTimeoutTimer(); void ScheduleNextTCPUserTimeoutPoll(uint32_t aTimeOut); #endif // INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT TCPEndPoint(const TCPEndPoint &) = delete; CHIP_ERROR DriveSending(); void DriveReceiving(); void HandleConnectComplete(CHIP_ERROR err); void HandleAcceptError(CHIP_ERROR err); void DoClose(CHIP_ERROR err, bool suppressCallback); static bool IsConnected(State state); static void TCPConnectTimeoutHandler(chip::System::Layer * aSystemLayer, void * aAppState); void StartConnectTimerIfSet(); void StopConnectTimer(); friend class TCPEndPointDeletor; /* * Implementation helpers for shared methods. */ virtual CHIP_ERROR BindImpl(IPAddressType addrType, const IPAddress & addr, uint16_t port, bool reuseAddr) = 0; virtual CHIP_ERROR ListenImpl(uint16_t backlog) = 0; virtual CHIP_ERROR ConnectImpl(const IPAddress & addr, uint16_t port, InterfaceId intfId) = 0; virtual CHIP_ERROR SendQueuedImpl(bool queueWasEmpty) = 0; virtual CHIP_ERROR SetUserTimeoutImpl(uint32_t userTimeoutMillis) = 0; virtual CHIP_ERROR DriveSendingImpl() = 0; virtual void HandleConnectCompleteImpl() = 0; virtual void DoCloseImpl(CHIP_ERROR err, State oldState) = 0; }; template <> struct EndPointProperties { static constexpr char kName[] = "TCP"; static constexpr size_t kNumEndPoints = INET_CONFIG_NUM_TCP_ENDPOINTS; static constexpr int kSystemStatsKey = System::Stats::kInetLayer_NumTCPEps; }; } // namespace Inet } // namespace chip