diff --git a/doc/ot_api_doc.h b/doc/ot_api_doc.h index 07ebf90d1..86a9768c2 100644 --- a/doc/ot_api_doc.h +++ b/doc/ot_api_doc.h @@ -215,6 +215,7 @@ * @defgroup plat-radio Radio * @defgroup plat-settings Settings * @defgroup plat-spi-slave SPI Slave + * @defgroup plat-tcp TCP - Platform * @defgroup plat-time Time Service * @defgroup plat-toolchain Toolchain * @defgroup plat-trel TREL - Platform diff --git a/etc/cmake/options.cmake b/etc/cmake/options.cmake index 343ce8a6f..769e25d07 100644 --- a/etc/cmake/options.cmake +++ b/etc/cmake/options.cmake @@ -250,6 +250,7 @@ ot_option(OT_PLATFORM_KEY_REF OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE " ot_option(OT_PLATFORM_LOG_CRASH_DUMP OPENTHREAD_CONFIG_PLATFORM_LOG_CRASH_DUMP_ENABLE "platform log crash dump") ot_option(OT_PLATFORM_NETIF OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE "platform netif") ot_option(OT_PLATFORM_POWER_CALIBRATION OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE "power calibration") +ot_option(OT_PLATFORM_TCP OPENTHREAD_CONFIG_PLATFORM_TCP_ENABLE "Platform TCP") ot_option(OT_PLATFORM_UDP OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE "platform UDP") ot_option(OT_REFERENCE_DEVICE OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE "test harness reference device") ot_option(OT_SEEKER OPENTHREAD_CONFIG_SEEKER_ENABLE "seeker") diff --git a/examples/platforms/simulation/CMakeLists.txt b/examples/platforms/simulation/CMakeLists.txt index 6506c413a..70abce298 100644 --- a/examples/platforms/simulation/CMakeLists.txt +++ b/examples/platforms/simulation/CMakeLists.txt @@ -90,6 +90,7 @@ add_library(openthread-simulation simul_utils.c spi-stubs.c system.c + tcp.c trel.c uart.c virtual_time/alarm-sim.c diff --git a/examples/platforms/simulation/tcp.c b/examples/platforms/simulation/tcp.c new file mode 100644 index 000000000..06aaebc9e --- /dev/null +++ b/examples/platforms/simulation/tcp.c @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2026, 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. + */ + +#include "platform-simulation.h" + +#include + +#if OPENTHREAD_CONFIG_PLATFORM_TCP_ENABLE + +otError otPlatTcpEnableListener(otPlatTcpListener *aListener, const otPlatTcpSockAddr *aLocalSockAddr) +{ + OT_UNUSED_VARIABLE(aListener); + OT_UNUSED_VARIABLE(aLocalSockAddr); + + return OT_ERROR_FAILED; +} + +void otPlatTcpDisableListener(otPlatTcpListener *aListener) { OT_UNUSED_VARIABLE(aListener); } + +otError otPlatTcpConnect(otPlatTcpConnection *aConn, + const otPlatTcpSockAddr *aPeerSockAddr, + const otPlatTcpSockAddr *aLocalSockAddr) +{ + OT_UNUSED_VARIABLE(aConn); + OT_UNUSED_VARIABLE(aPeerSockAddr); + OT_UNUSED_VARIABLE(aLocalSockAddr); + + return OT_ERROR_FAILED; +} + +void otPlatTcpNotifyTxPending(otPlatTcpConnection *aConn) { OT_UNUSED_VARIABLE(aConn); } + +uint16_t otPlatTcpSend(otPlatTcpConnection *aConn, const uint8_t *aBuffer, uint16_t aLength) +{ + OT_UNUSED_VARIABLE(aConn); + OT_UNUSED_VARIABLE(aBuffer); + OT_UNUSED_VARIABLE(aLength); + + return 0; +} + +void otPlatTcpClose(otPlatTcpConnection *aConn) { OT_UNUSED_VARIABLE(aConn); } + +void otPlatTcpAbort(otPlatTcpConnection *aConn) { OT_UNUSED_VARIABLE(aConn); } + +#endif // #if OPENTHREAD_CONFIG_PLATFORM_TCP_ENABLE diff --git a/include/openthread/BUILD.gn b/include/openthread/BUILD.gn index 3c6d3dc20..528ef5fa3 100644 --- a/include/openthread/BUILD.gn +++ b/include/openthread/BUILD.gn @@ -112,6 +112,7 @@ source_set("openthread") { "platform/radio.h", "platform/settings.h", "platform/spi-slave.h", + "platform/tcp.h", "platform/time.h", "platform/toolchain.h", "platform/trel.h", diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 82ab52ee3..d9765b288 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -52,7 +52,7 @@ extern "C" { * * @note This number versions both OpenThread platform and user APIs. */ -#define OPENTHREAD_API_VERSION (604) +#define OPENTHREAD_API_VERSION (605) /** * @addtogroup api-instance diff --git a/include/openthread/platform/tcp.h b/include/openthread/platform/tcp.h new file mode 100644 index 000000000..df9432c6b --- /dev/null +++ b/include/openthread/platform/tcp.h @@ -0,0 +1,439 @@ +/* + * Copyright (c) 2026, 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 + * @brief + * This file includes the abstraction for the platform TCP + */ + +#ifndef OPENTHREAD_PLATFORM_TCP_H_ +#define OPENTHREAD_PLATFORM_TCP_H_ + +#include +#include + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @addtogroup plat-tcp + * + * @brief + * This module includes the platform abstraction for TCP connections and listeners. + * + * All APIs in this module are applicable only when `OPENTHREAD_CONFIG_PLATFORM_TCP_ENABLE` feature is enabled. + * + * @{ + */ + +/** + * Represents platform-specific data associated with a connection or a listener. + * + * This union is provided to add flexibility for the platform. A platform can choose to store a file descriptor + * (e.g., an `int` for a POSIX socket) or a pointer to an arbitrary context or state structure needed by the + * platform implementation. + * + * The OpenThread stack guarantees that the `otPlatTcpPlatformData` is fully cleared (all bytes set to zero) when a + * new listener or connection instance is initialized. + * + * For an `otPlatTcpListener`, the `otPlatTcpEnableListener()` call provides an opportunity for the platform to allocate + * or update this information. The OpenThread stack guarantees that `otPlatTcpDisableListener()` will be invoked on any + * previously enabled listener, providing a deterministic point for the platform implementation to perform cleanup + * (e.g., deallocating memory or context structures). + * + * For an `otPlatTcpConnection`, the `otPlatTcpConnect()` call or the `otPlatTcpAccept()` callback indicate when a new + * connection instance is provided, allowing the platform data to be initialized. The platform is responsible for + * cleaning up this data either before invoking `otPlatTcpHandleDisconnected()` (which invalidates the connection) or + * from an `otPlatTcpAbort()` call. The OpenThread stack guarantees that it will eventually disconnect or abort any + * active connection, ensuring a reliable cleanup path. + */ +typedef union +{ + int mDescriptor; ///< A value (like a file descriptor). + void *mContext; ///< Pointer to arbitrary platform data. +} otPlatTcpPlatformData; + +/** + * Represents a TCP listener. + * + * The OpenThread core owns and manages the `otPlatTcpListener` instances. The platform should track the pointers + * to these instances and use them when invoking the callbacks. The `otPlatTcpListener *` can be viewed as + * a "descriptor" or "handle" to the listener. + */ +typedef struct otPlatTcpListener +{ + otPlatTcpPlatformData mData; ///< Platform implementation specific data. +} otPlatTcpListener; + +/** + * Represents a TCP connection. + * + * The OpenThread core owns and manages the `otPlatTcpConnection` instances. The platform should track the pointers + * to these instances and pass them when invoking the `otPlatTcpHandle*` callbacks. The `otPlatTcpConnection *` can + * be viewed as a "descriptor" or "handle" to the connection. + * + * The `otPlatTcpConnection` instance remains valid as long as the connection is active. + */ +typedef struct otPlatTcpConnection +{ + otPlatTcpPlatformData mData; ///< Platform implementation specific data. +} otPlatTcpConnection; + +/** + * Represents a TCP socket address. + */ +typedef struct otPlatTcpSockAddr +{ + otSockAddr mSockAddr; ///< The socket address (IP address and port number). Use IPv4-mapped IPv6 for IPv4. + uint32_t mIfIndex; ///< Interface index. Zero indicates any/unspecified. +} otPlatTcpSockAddr; + +/** + * Defines the reason for a TCP connection disconnection. + */ +typedef enum otPlatTcpDisconnectReason +{ + OT_PLAT_TCP_DISCONNECT_REASON_CLOSED, ///< Connection was gracefully closed. + OT_PLAT_TCP_DISCONNECT_REASON_TIMEOUT, ///< Connection timed out (e.g., failed to connect or keepalive failure). + OT_PLAT_TCP_DISCONNECT_REASON_REFUSED, ///< Connection was refused by the peer (RST received during handshake). + OT_PLAT_TCP_DISCONNECT_REASON_RESET, ///< Connection was reset by the peer (RST received on established conn). + OT_PLAT_TCP_DISCONNECT_REASON_ERROR, ///< Connection was aborted due to other errors. +} otPlatTcpDisconnectReason; + +/** + * Enables a TCP listener. + * + * The platform should start listening for incoming TCP connections on the provided @p aLocalSockAddr. When an + * incoming connection request is received, the platform must invoke the `otPlatTcpAccept()` callback to accept the + * request. + * + * The @p aLocalSockAddr specifies the local interface, address, and port to bind to. Importantly, the port number + * within @p aLocalSockAddr must not be zero. The IP address may be unspecified (all zeros) to indicate that the + * listener should accept connections on any local address. + * + * @param[in] aListener The TCP listener. + * @param[in] aLocalSockAddr The local socket address to listen on. + * + * @retval OT_ERROR_NONE Successfully enabled or disabled the listener. + * @retval OT_ERROR_ALREADY Already listening on the same port/address. + * @retval OT_ERROR_FAILED Failed to enable the listener. + */ +otError otPlatTcpEnableListener(otPlatTcpListener *aListener, const otPlatTcpSockAddr *aLocalSockAddr); + +/** + * Disables a TCP listener. + * + * The platform should stop listening for incoming connections on the socket associated with the listener. + * Any incoming connection requests that have not yet been accepted should be discarded. + * + * @param[in] aListener The TCP listener. + */ +void otPlatTcpDisableListener(otPlatTcpListener *aListener); + +/** + * Callback to accept an incoming TCP connection request on an active listener. + * + * This function is implemented and provided by the OpenThread stack for the platform to use. + * + * The callback returns a pointer to an `otPlatTcpConnection` for the new connection. If the callback returns NULL, + * the incoming connection is rejected. + * + * @param[in] aListener The TCP listener. + * @param[in] aPeerSockAddr The peer's socket address. + * + * @returns A pointer for the newly accepted connection, or NULL to reject the connection request. + */ +extern otPlatTcpConnection *otPlatTcpAccept(otPlatTcpListener *aListener, const otPlatTcpSockAddr *aPeerSockAddr); + +/** + * Initiates a TCP connection to a peer. + * + * The platform should initiate a TCP connection to the @p aPeerSockAddr. + * + * The @p aLocalSockAddr specifies the local address and port to bind to before connecting. It can be NULL if the + * OpenThread stack does not specify a preference. If provided, fields within @p aLocalSockAddr may still be left + * unspecified (e.g., the IP address can be all zeros, or the port can be zero). In all such cases, the platform + * and the underlying TCP stack should automatically select an appropriate local IP address and/or an ephemeral port. + * + * If `OT_ERROR_NONE` is returned (indicating successful initialization of the connection process), the platform must + * subsequently report the status. Upon successful connection establishment, the platform must invoke the + * `otPlatTcpHandleConnected` callback. If it fails to establish the connection, the `otPlatTcpHandleDisconnected` + * callback must be called to indicate the failure. + * + * @param[in] aConn The TCP connection. + * @param[in] aPeerSockAddr The peer's socket address. + * @param[in] aLocalSockAddr The local socket address. Can be NULL. + * + * @retval OT_ERROR_NONE Successfully initiated the connection. + * @retval OT_ERROR_FAILED Failed to initiate the connection. + */ +otError otPlatTcpConnect(otPlatTcpConnection *aConn, + const otPlatTcpSockAddr *aPeerSockAddr, + const otPlatTcpSockAddr *aLocalSockAddr); + +/** + * Indicates whether the TCP connection is currently in the connecting state. + * + * This function is provided by the OpenThread stack. The platform can use it to determine if a TCP connection is still + * waiting for the TCP handshake to complete. + * + * @param[in] aConn The TCP connection. + * + * @retval TRUE The connection is currently in the connecting state. + * @retval FALSE The connection is not in the connecting state. + */ +extern bool otPlatTcpIsConnecting(otPlatTcpConnection *aConn); + +/** + * Callback to notify the connection establishment. + * + * This callback is implemented and provided by the OpenThread stack. It must be invoked by the platform to indicate + * that the TCP handshake is complete and that the connection is now established. + * + * The platform must call this after a successful call to `otPlatTcpConnect()` when the connection is established. + * For incoming connection requests (on an `otPlatTcpListener`), the platform must call this after the + * `otPlatTcpAccept()` callback returns successfully and when the connection is established. + * + * @param[in] aConn The TCP connection. + */ +extern void otPlatTcpHandleConnected(otPlatTcpConnection *aConn); + +/** + * Notifies the platform that there is pending data for transmission. + * + * This function is called by the OpenThread stack when it has new data for transmission. After this call, the platform + * should indicate when it is ready to accept the data by invoking the `otPlatTcpHandleTxReady()` callback. + * + * The platform can also use `otPlatTcpIsTxPending()` to check if there is pending data for transmission. + * + * It is permissible for the platform implementation to invoke the `otPlatTcpHandleTxReady()` callback directly from + * within `otPlatTcpNotifyTxPending()` before returning, if the underlying TCP transmit buffer is already available. + * The OpenThread stack will handle this correctly. + * + * @param[in] aConn The TCP connection. + */ +void otPlatTcpNotifyTxPending(otPlatTcpConnection *aConn); + +/** + * Indicates whether the TCP connection has pending data for transmission. + * + * This function is provided by the OpenThread stack. The platform can use it to check if there is any pending data + * for transmission over the TCP connection. + * + * @param[in] aConn The TCP connection. + * + * @retval TRUE The connection has pending transmit data. + * @retval FALSE The connection does not have pending transmit data. + */ +extern bool otPlatTcpIsTxPending(otPlatTcpConnection *aConn); + +/** + * Callback to notify that the platform is ready to accept more transmit data. + * + * This function is implemented and provided by the OpenThread stack for the platform to use. + * + * The platform should invoke this callback when it is ready to accept more data for transmission over the TCP + * connection, in response to a prior `otPlatTcpNotifyTxPending()` call. Upon being called, the OpenThread stack will + * use `otPlatTcpSend()` to provide the pending TX data to the platform. The stack may call `otPlatTcpSend()` multiple + * times during the execution of this callback. + * + * @param[in] aConn The TCP connection. + */ +extern void otPlatTcpHandleTxReady(otPlatTcpConnection *aConn); + +/** + * Sends data over an active TCP connection. + * + * This function is called by the OpenThread stack to provide data for the platform to transmit. The data is provided + * in a buffer. The platform should copy as much data as it can from the given buffer into its underlying platform + * transmit buffer. + * + * The provided @p aBuffer is temporary. The platform must not store the pointer or assume the content remains valid + * after this function returns. All required data must be copied during this call. + * + * The OpenThread stack typically invokes this function from the `otPlatTcpHandleTxReady()` callback. However, the + * platform implementation must not assume this and should support being called at any time. If there is no space + * available to accept any data, the platform can return zero. + * + * The OpenThread stack may call this function multiple times back-to-back to provide all queued transmit content + * in chunks. The platform should be prepared to handle consecutive calls efficiently. + * + * @param[in] aConn The TCP connection. + * @param[in] aBuffer A pointer to the buffer containing the data to send. + * @param[in] aLength The length (in bytes) of the data in the buffer. + * + * @returns The actual number of bytes accepted for transmission. + */ +uint16_t otPlatTcpSend(otPlatTcpConnection *aConn, const uint8_t *aBuffer, uint16_t aLength); + +/** + * Callback to notify the reception of data on a connection. + * + * This function is implemented and provided by the OpenThread stack for the platform to use. + * + * The platform invokes this callback to provide received data to the OpenThread stack. The provided @p aBuffer + * only needs to remain valid for the duration of this call. The OpenThread stack will process and copy the + * bytes as needed, and will not retain the @p aBuffer pointer after the function returns. + * + * Since TCP is a stream protocol, data can arrive in arbitrarily sized chunks. The platform does not need to + * buffer or reassemble these; it can invoke this callback immediately as data is received, even if it is expecting + * more data. The OpenThread stack handles all stream-level behavior, processing, and retention of the + * received data. This helps simplify the platform implementation. + * + * On certain platforms (such as standard POSIX), a return value of 0 from `read()` or `recv()` indicates an + * End-of-File (EOF) or graceful closure by the peer. The platform implementation must check for this condition and + * report it by invoking `otPlatTcpHandleDisconnected()` with the reason set to `OT_PLAT_TCP_DISCONNECT_REASON_CLOSED`. + * Importantly, calling `otPlatTcpHandleReceive()` with `aLength` set to zero does not signify a graceful closure in + * the `otPlatTcp` APIs; such a call is treated as a no-op receive event by the OpenThread stack and is ignored. + * + * @param[in] aConn The TCP connection. + * @param[in] aBuffer A pointer to the buffer containing the received data. Must not be NULL if @p aLength > 0. + * @param[in] aLength The length (in bytes) of the received data. + */ +extern void otPlatTcpHandleReceive(otPlatTcpConnection *aConn, const uint8_t *aBuffer, uint16_t aLength); + +/** + * Gracefully closes the TCP connection. + * + * This function initiates a graceful closure of the connection. The platform should transmit any remaining data + * before performing the standard TCP connection termination. + * + * Once the connection is fully disconnected, or if it is already closed, or if an error occurs during the close + * operation, the platform must indicate this by invoking the `otPlatTcpHandleDisconnected` callback. + * + * The platform must always call `otPlatTcpHandleDisconnected()` to report the final outcome of the connection. + * It is permissible for the platform implementation to invoke this callback directly from within `otPlatTcpClose()` + * before returning. The OpenThread stack will handle this correctly. + * + * @param[in] aConn The TCP connection. + */ +void otPlatTcpClose(otPlatTcpConnection *aConn); + +/** + * Aborts the TCP connection. + * + * This function forcefully terminates the connection. Any unsent data is discarded. + * + * After this call, the platform must forget the @p aConn. Importantly, it must not invoke any callbacks using the + * @p aConn any longer, including `otPlatTcpHandleDisconnected`. This effectively indicates to the platform that the + * OpenThread core is de-allocating the @p aConn instance and it is no longer valid. + * + * @param[in] aConn The TCP connection. + */ +void otPlatTcpAbort(otPlatTcpConnection *aConn); + +/** + * Callback to notify the connection disconnection. + * + * This function is implemented and provided by the OpenThread stack for the platform to use. + * + * This callback should be invoked by the platform when it fails to establish a connection, when an established + * connection is successfully closed (by both endpoints), when it times out, or is reset or aborted. + * + * After this callback is invoked, the `otPlatTcpConnection` instance is no longer valid. The platform must not use it + * in any future callbacks. + * + * @param[in] aConn The TCP connection. + * @param[in] aReason The reason for the disconnection. + */ +extern void otPlatTcpHandleDisconnected(otPlatTcpConnection *aConn, otPlatTcpDisconnectReason aReason); + +/** + * Gets the OpenThread instance associated with a given TCP connection. + * + * This function is provided by OpenThread core. Platform implementations can use it to get the OpenThread instance + * associated with an active `otPlatTcpConnection`. + * + * @param[in] aConn The TCP connection. + * + * @returns The OpenThread instance. + */ +extern otInstance *otPlatTcpGetInstanceForConnection(otPlatTcpConnection *aConn); + +/** + * Gets the OpenThread instance associated with a given TCP listener. + * + * This function is provided by OpenThread core. Platform implementations can use it to get the OpenThread instance + * associated with an active `otPlatTcpListener`. + * + * @param[in] aListener The TCP listener. + * + * @returns The OpenThread instance. + */ +extern otInstance *otPlatTcpGetInstanceForListener(otPlatTcpListener *aListener); + +/** + * Iterates through the active TCP listeners. + * + * This function can be used to iterate over all currently active TCP listeners associated with the OpenThread + * instance. It allows platform implementations to process or manage listeners without needing to maintain their own + * list of active listeners. + * + * The iteration is guaranteed to remain consistent even if callbacks (e.g., `otPlatTcpAccept`) are invoked during the + * process. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aPrevListener A pointer to the previous listener, or `NULL` to start the iteration from the beginning. + * + * @returns A pointer to the next listener, or `NULL` if there are no more listeners. + */ +extern otPlatTcpListener *otPlatTcpIterateListeners(otInstance *aInstance, otPlatTcpListener *aPrevListener); + +/** + * Iterates through the active TCP connections. + * + * This function can be used to iterate over all currently active TCP connections associated with the OpenThread + * instance. It allows platform implementations to process or manage connections without needing to maintain their own + * list of active connections. + * + * The iteration is guaranteed to remain consistent and safe even if callbacks are invoked during the process. For + * example, if a connection is reported as disconnected via `otPlatTcpHandleDisconnected` during iteration, the + * OpenThread stack ensures that the connection entry remains valid until the iteration is completed. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aPrevConn A pointer to the previous connection, or `NULL` to start the iteration from the beginning. + * + * @returns A pointer to the next connection, or `NULL` if there are no more connections. + */ +extern otPlatTcpConnection *otPlatTcpIterateConnections(otInstance *aInstance, otPlatTcpConnection *aPrevConn); + +/** + * @} + */ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // OPENTHREAD_PLATFORM_TCP_H_ diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index 94dbe1ff8..921164396 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -636,6 +636,8 @@ openthread_core_files = [ "net/nd_agent.hpp", "net/netif.cpp", "net/netif.hpp", + "net/plat_tcp.cpp", + "net/plat_tcp.hpp", "net/slaac_address.cpp", "net/slaac_address.hpp", "net/sntp_client.cpp", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index fe69fdcfb..c970102e5 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -210,6 +210,7 @@ set(COMMON_SOURCES net/nd6.cpp net/nd_agent.cpp net/netif.cpp + net/plat_tcp.cpp net/slaac_address.cpp net/sntp_client.cpp net/socket.cpp diff --git a/src/core/common/message.hpp b/src/core/common/message.hpp index 928c1b2d4..67e8f0065 100644 --- a/src/core/common/message.hpp +++ b/src/core/common/message.hpp @@ -81,6 +81,10 @@ class HmacSha256; } // namespace Crypto +namespace Ip6 { +class PlatTcp; +} // namespace Ip6 + /** * @addtogroup core-message * @@ -293,6 +297,7 @@ class Message : public otMessage, public Buffer, public GetProvider friend class Crypto::HmacSha256; friend class Crypto::Sha256; friend class Crypto::AesCcm; + friend class Ip6::PlatTcp; friend class MessagePool; friend class MessageQueue; friend class PriorityQueue; @@ -1714,6 +1719,14 @@ public: ~MessageQueue(void) { DequeueAndFreeAll(); } #endif + /** + * Indicates whether the message queue is empty. + * + * @retval TRUE The message queue is empty. + * @retval FALSE The message queue is not empty. + */ + bool IsEmpty(void) const { return GetHead() == nullptr; } + /** * Returns a pointer to the first message. * diff --git a/src/core/config/ip6.h b/src/core/config/ip6.h index eb8a54d36..ed6e4e84f 100644 --- a/src/core/config/ip6.h +++ b/src/core/config/ip6.h @@ -198,6 +198,15 @@ #define OPENTHREAD_CONFIG_MPL_DYNAMIC_INTERVAL_ENABLE 0 #endif +/** + * @def OPENTHREAD_CONFIG_PLATFORM_TCP_ENABLE + * + * Define as 1 to enable TCP support via platform `otPlatTcp*` APIs and `Ip6::PlatTcp`. + */ +#ifndef OPENTHREAD_CONFIG_PLATFORM_TCP_ENABLE +#define OPENTHREAD_CONFIG_PLATFORM_TCP_ENABLE 0 +#endif + /** * @def OPENTHREAD_CONFIG_TCP_ENABLE * diff --git a/src/core/instance/instance.cpp b/src/core/instance/instance.cpp index c37ce67f1..6b5daac64 100644 --- a/src/core/instance/instance.cpp +++ b/src/core/instance/instance.cpp @@ -110,6 +110,9 @@ Instance::Instance(void) , mIp6(*this) , mThreadNetif(*this) , mTmfAgent(*this) +#if OPENTHREAD_CONFIG_PLATFORM_TCP_ENABLE + , mPlatTcp(*this) +#endif #if OPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE , mDhcp6Client(*this) #endif diff --git a/src/core/instance/instance.hpp b/src/core/instance/instance.hpp index 64e94ef2c..1f0c77f62 100644 --- a/src/core/instance/instance.hpp +++ b/src/core/instance/instance.hpp @@ -116,6 +116,7 @@ #include "net/nat64_translator.hpp" #include "net/nd_agent.hpp" #include "net/netif.hpp" +#include "net/plat_tcp.hpp" #include "net/slaac_address.hpp" #include "net/sntp_client.hpp" #include "net/srp_advertising_proxy.hpp" @@ -631,6 +632,10 @@ private: ThreadNetif mThreadNetif; Tmf::Agent mTmfAgent; +#if OPENTHREAD_CONFIG_PLATFORM_TCP_ENABLE + Ip6::PlatTcp mPlatTcp; +#endif + #if OPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE Dhcp6::Client mDhcp6Client; #endif @@ -1065,6 +1070,10 @@ template <> inline Ip6::Icmp &Instance::Get(void) { return mIp6.mIcmp; } template <> inline Ip6::Mpl &Instance::Get(void) { return mIp6.mMpl; } +#if OPENTHREAD_CONFIG_PLATFORM_TCP_ENABLE +template <> inline Ip6::PlatTcp &Instance::Get(void) { return mPlatTcp; } +#endif + template <> inline Tmf::Agent &Instance::Get(void) { return mTmfAgent; } #if OPENTHREAD_CONFIG_SECURE_TRANSPORT_ENABLE diff --git a/src/core/net/ip6_address.hpp b/src/core/net/ip6_address.hpp index 945c2fa20..338fca848 100644 --- a/src/core/net/ip6_address.hpp +++ b/src/core/net/ip6_address.hpp @@ -879,6 +879,15 @@ public: */ void ToString(char *aBuffer, uint16_t aSize) const; + /** + * Appends the IPv6 address to a given `StringWriter`. + * + * The IPv6 address string is formatted as 16 hex values separated by ':' (i.e., "%x:%x:%x:...:%x"). + * + * @param[in,out] aWriter A reference to a `StringWriter` to append the string to. + */ + void ToString(StringWriter &aWriter) const; + /** * Overloads operator `<` to compare two IPv6 addresses. * @@ -893,7 +902,6 @@ private: static constexpr uint8_t kMulticastNetworkPrefixLengthOffset = 3; // Prefix-Based Multicast Address (RFC3306) static constexpr uint8_t kMulticastNetworkPrefixOffset = 4; // Prefix-Based Multicast Address (RFC3306) - void ToString(StringWriter &aWriter) const; void AppendHexWords(StringWriter &aWriter, uint8_t aLength) const; static void CopyBits(uint8_t *aDst, const uint8_t *aSrc, uint8_t aNumBits); diff --git a/src/core/net/plat_tcp.cpp b/src/core/net/plat_tcp.cpp new file mode 100644 index 000000000..4cf32b465 --- /dev/null +++ b/src/core/net/plat_tcp.cpp @@ -0,0 +1,677 @@ +/* + * Copyright (c) 2026, 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 platform TCP. + */ + +#include "plat_tcp.hpp" + +#if OPENTHREAD_CONFIG_PLATFORM_TCP_ENABLE + +#include "instance/instance.hpp" + +namespace ot { +namespace Ip6 { + +RegisterLogModule("PlatTcp"); + +//--------------------------------------------------------------------------------------------------------------------- +// otPlatTcp functions (callbacks) + +extern "C" otPlatTcpConnection *otPlatTcpAccept(otPlatTcpListener *aListener, const otPlatTcpSockAddr *aPeerSockAddr) +{ + return AsCoreType(aListener).Accept(AsCoreType(aPeerSockAddr)); +} + +extern "C" bool otPlatTcpIsConnecting(otPlatTcpConnection *aConn) +{ + return AsCoreType(aConn).GetState() == PlatTcp::Connection::kStateConnecting; +} + +extern "C" void otPlatTcpHandleConnected(otPlatTcpConnection *aConn) { AsCoreType(aConn).HandleConnected(); } + +extern "C" bool otPlatTcpIsTxPending(otPlatTcpConnection *aConn) { return AsCoreType(aConn).IsTxPending(); } + +extern "C" void otPlatTcpHandleTxReady(otPlatTcpConnection *aConn) { AsCoreType(aConn).HandleTxReady(); } + +extern "C" void otPlatTcpHandleReceive(otPlatTcpConnection *aConn, const uint8_t *aBuffer, uint16_t aLength) +{ + AsCoreType(aConn).HandleReceive(aBuffer, aLength); +} + +extern "C" void otPlatTcpHandleDisconnected(otPlatTcpConnection *aConn, otPlatTcpDisconnectReason aReason) +{ + AsCoreType(aConn).HandleDisconnected(static_cast(aReason)); +} + +extern "C" otInstance *otPlatTcpGetInstanceForConnection(otPlatTcpConnection *aConn) +{ + return &AsCoreType(aConn).GetInstance(); +} + +extern "C" otInstance *otPlatTcpGetInstanceForListener(otPlatTcpListener *aListener) +{ + return &AsCoreType(aListener).GetInstance(); +} + +extern "C" otPlatTcpListener *otPlatTcpIterateListeners(otInstance *aInstance, otPlatTcpListener *aPrevListener) +{ + return AsCoreType(aInstance).Get().IterateListeners(AsCoreTypePtr(aPrevListener)); +} + +extern "C" otPlatTcpConnection *otPlatTcpIterateConnections(otInstance *aInstance, otPlatTcpConnection *aPrevConn) +{ + return AsCoreType(aInstance).Get().IterateConnections(AsCoreTypePtr(aPrevConn)); +} + +//--------------------------------------------------------------------------------------------------------------------- +// PlatTcp::SockAddr + +bool PlatTcp::SockAddr::IsAllUnspecified(void) const +{ + return (GetPort() == 0) && (mIfIndex == 0) && GetAddress().IsUnspecified(); +} + +PlatTcp::SockAddr::InfoString PlatTcp::SockAddr::ToString(void) const +{ + InfoString string; + + string.Append("["); + GetAddress().ToString(string); + string.Append("%%%lu]:%u", ToUlong(GetIfIndex()), GetPort()); + + return string; +} + +//--------------------------------------------------------------------------------------------------------------------- +// PlatTcp::Listener + +PlatTcp::Listener::Listener(Instance &aInstance, AcceptHandler aAcceptHandler, DeleteHandler aDeleteHandler) + : InstanceLocator(aInstance) + , mNext(nullptr) + , mState(kStateUnused) + , mAcceptHandler(aAcceptHandler) + , mDeleteHandler(aDeleteHandler) +{ + ClearAllBytes(mData); +} + +Error PlatTcp::Listener::Enable(const SockAddr &aLocalSockAddr) +{ + Error error = kErrorNone; + + switch (GetState()) + { + case kStateUnused: + Get().AddListener(*this); + break; + case kStateDisabled: + break; + case kStateEnabled: + error = kErrorAlready; + ExitNow(); + } + + ClearAllBytes(mData); + mLocalSockAddr = aLocalSockAddr; + + SetState(kStateEnabled); + + error = otPlatTcpEnableListener(this, &mLocalSockAddr); + + if (error != kErrorNone) + { + SetState(kStateDisabled); + } + +exit: + return error; +} + +void PlatTcp::Listener::Disable(void) +{ + VerifyOrExit(GetState() == kStateEnabled); + + SetState(kStateDisabled); + + otPlatTcpDisableListener(this); + +exit: + return; +} + +void PlatTcp::Listener::SetState(State aState) +{ + VerifyOrExit(mState != aState); + + mState = aState; + + switch (mState) + { + case kStateUnused: + VerifyOrExit(mDeleteHandler != nullptr); + mDeleteHandler(*this); + break; + + case kStateEnabled: + break; + + case kStateDisabled: + Get().PostListenerTask(); + break; + } + +exit: + return; +} + +PlatTcp::Connection *PlatTcp::Listener::Accept(const SockAddr &aPeerSockAddr) +{ + Connection *connection = nullptr; + + VerifyOrExit(GetState() == kStateEnabled); + + VerifyOrExit(mAcceptHandler != nullptr); + + connection = mAcceptHandler(*this, aPeerSockAddr); + VerifyOrExit(connection != nullptr); + + if (connection->Prepare(mLocalSockAddr, aPeerSockAddr) != kErrorNone) + { + connection = nullptr; + } + +exit: + return connection; +} + +//--------------------------------------------------------------------------------------------------------------------- +// PlatTcp::Connection + +PlatTcp::Connection::Connection(Instance &aInstance, EventHandler aEventHandler, DeleteHandler aDeleteHandler) + : InstanceLocator(aInstance) + , mNext(nullptr) + , mState(kStateUnused) + , mDisconnectReason(kDisconnectReasonClosed) + , mRxMessage(nullptr) + , mEventHandler(aEventHandler) + , mDeleteHandler(aDeleteHandler) +{ + ClearAllBytes(mData); +} + +void PlatTcp::Connection::SetState(State aState) +{ + VerifyOrExit(mState != aState); + + mState = aState; + + switch (mState) + { + case kStateUnused: + FreeRxMessage(); + mTxQueue.DequeueAndFreeAll(); + VerifyOrExit(mDeleteHandler != nullptr); + mDeleteHandler(*this); + break; + + case kStateConnecting: + case kStateConnected: + case kStateToClose: + case kStateClosing: + break; + + case kStateDisconnected: + mTxQueue.DequeueAndFreeAll(); + Get().PostConnectionTask(); + break; + } + +exit: + return; +} + +Error PlatTcp::Connection::Prepare(const SockAddr &aLocalSockAddr, const SockAddr &aPeerSockAddr) +{ + Error error = kErrorNone; + + switch (GetState()) + { + case kStateUnused: + Get().AddConnection(*this); + break; + case kStateDisconnected: + break; + + case kStateConnecting: + case kStateConnected: + case kStateToClose: + case kStateClosing: + error = kErrorInvalidState; + ExitNow(); + } + + ClearAllBytes(mData); + FreeRxMessage(); + mLocalSockAddr = aLocalSockAddr; + mPeerSockAddr = aPeerSockAddr; + + SetState(kStateConnecting); + +exit: + return error; +} + +Error PlatTcp::Connection::Connect(const SockAddr &aPeerSockAddr) { return BindAndConnect(SockAddr(), aPeerSockAddr); } + +Error PlatTcp::Connection::BindAndConnect(const SockAddr &aLocalSockAddr, const SockAddr &aPeerSockAddr) +{ + Error error; + + SuccessOrExit(error = Prepare(aLocalSockAddr, aPeerSockAddr)); + + error = otPlatTcpConnect(this, &mPeerSockAddr, mLocalSockAddr.IsAllUnspecified() ? nullptr : &mLocalSockAddr); + + if (error != kErrorNone) + { + SetState(kStateDisconnected); + } + +exit: + return error; +} + +void PlatTcp::Connection::HandleConnected(void) +{ + VerifyOrExit(GetState() == kStateConnecting); + + SetState(kStateConnected); + SignalEvent(kEventConnected); + +exit: + return; +} + +Error PlatTcp::Connection::Send(OwnedPtr aMessage) +{ + Error error = kErrorNone; + bool shouldNotify; + + VerifyOrExit(GetState() == kStateConnected, error = kErrorInvalidState); + + VerifyOrExit(aMessage != nullptr); + VerifyOrExit(aMessage->GetLength() > 0); + + aMessage->SetOffset(0); + + shouldNotify = mTxQueue.IsEmpty(); + mTxQueue.Enqueue(*aMessage.Release()); + + if (shouldNotify) + { + otPlatTcpNotifyTxPending(this); + } + +exit: + return error; +} + +void PlatTcp::Connection::HandleTxReady(void) +{ + switch (GetState()) + { + case kStateConnected: + case kStateToClose: + break; + case kStateUnused: + case kStateConnecting: + case kStateClosing: + case kStateDisconnected: + ExitNow(); + } + + while (!mTxQueue.IsEmpty()) + { + Message &message = *mTxQueue.GetHead(); + uint16_t remainingLength = message.DetermineLengthAfterOffset(); + uint16_t bytesSent; + Message::Chunk chunk; + + message.GetFirstChunk(message.GetOffset(), remainingLength, chunk); + + while (chunk.GetLength() > 0) + { + bytesSent = otPlatTcpSend(this, chunk.GetBytes(), chunk.GetLength()); + + message.SetOffset(message.GetOffset() + bytesSent); + + if (bytesSent < chunk.GetLength()) + { + otPlatTcpNotifyTxPending(this); + ExitNow(); + } + + message.GetNextChunk(remainingLength, chunk); + } + + mTxQueue.DequeueAndFree(message); + } + + SignalEvent(kEventSendDone); + + if (GetState() == kStateToClose) + { + Close(); + } + +exit: + return; +} + +void PlatTcp::Connection::HandleReceive(const uint8_t *aBuffer, uint16_t aLength) +{ + Error error = kErrorNone; + + VerifyOrExit(aLength > 0); + + switch (GetState()) + { + case kStateConnecting: + case kStateConnected: + case kStateToClose: + case kStateClosing: + break; + case kStateUnused: + case kStateDisconnected: + ExitNow(); + } + + if (mRxMessage == nullptr) + { + mRxMessage = Get().Allocate(Message::kTypeOther); + VerifyOrExit(mRxMessage != nullptr, error = kErrorNoBufs); + } + + mRxMessage->SetOffset(mRxMessage->GetLength()); + + SuccessOrExit(error = mRxMessage->AppendBytes(aBuffer, aLength)); + + SignalEvent(kEventReceive); + +exit: + if (error != kErrorNone) + { + Abort(); + mDisconnectReason = kDisconnectReasonNoBufs; + SignalEvent(kEventDisconnected); + } +} + +void PlatTcp::Connection::RemoveParsedLengthFromRxMessage(uint16_t aRemoveLength) +{ + uint16_t newLength = 0; + uint16_t newOffset = 0; + + VerifyOrExit(mRxMessage != nullptr); + + if (mRxMessage->GetLength() > aRemoveLength) + { + newLength = mRxMessage->GetLength() - aRemoveLength; + newOffset = (mRxMessage->GetOffset() > aRemoveLength) ? mRxMessage->GetOffset() - aRemoveLength : 0; + + mRxMessage->WriteBytesFromMessage(/* aWriteOffset */ 0, *mRxMessage, /* aReadOffset */ aRemoveLength, + newLength); + } + + IgnoreError(mRxMessage->SetLength(newLength)); + mRxMessage->SetOffset(newOffset); + +exit: + return; +} + +void PlatTcp::Connection::FreeRxMessage(void) +{ + VerifyOrExit(mRxMessage != nullptr); + + mRxMessage->Free(); + mRxMessage = nullptr; + +exit: + return; +} + +void PlatTcp::Connection::Close(void) +{ + switch (GetState()) + { + case kStateConnecting: + case kStateConnected: + case kStateToClose: + break; + + case kStateUnused: + case kStateDisconnected: + case kStateClosing: + ExitNow(); + } + + if (!mTxQueue.IsEmpty()) + { + SetState(kStateToClose); + } + else + { + SetState(kStateClosing); + otPlatTcpClose(this); + } + +exit: + return; +} + +void PlatTcp::Connection::Abort(void) +{ + switch (GetState()) + { + case kStateConnecting: + case kStateConnected: + case kStateToClose: + case kStateClosing: + break; + + case kStateUnused: + case kStateDisconnected: + ExitNow(); + } + + mDisconnectReason = kDisconnectReasonAbort; + SetState(kStateDisconnected); + + otPlatTcpAbort(this); + +exit: + return; +} + +void PlatTcp::Connection::HandleDisconnected(DisconnectReason aReason) +{ + switch (GetState()) + { + case kStateConnecting: + case kStateConnected: + case kStateToClose: + case kStateClosing: + break; + + case kStateUnused: + case kStateDisconnected: + ExitNow(); + } + + mDisconnectReason = aReason; + SetState(kStateDisconnected); + + SignalEvent(kEventDisconnected); + +exit: + return; +} + +void PlatTcp::Connection::SignalEvent(Event aEvent) +{ + VerifyOrExit(mEventHandler != nullptr); + mEventHandler(*this, aEvent); + +exit: + return; +} + +const char *PlatTcp::Connection::EventToString(Event aEvent) +{ +#define ConnEventMapList(_) \ + _(kEventConnected, "Connected") \ + _(kEventDisconnected, "Disconnected") \ + _(kEventSendDone, "SendDone") \ + _(kEventReceive, "Receive") + + DefineEnumStringArray(ConnEventMapList); + + return kStrings[aEvent]; +} + +//--------------------------------------------------------------------------------------------------------------------- +// PlatTcp + +PlatTcp::PlatTcp(Instance &aInstance) + : InstanceLocator(aInstance) + , mListenerTask(aInstance) + , mConnectionTask(aInstance) +{ +} + +PlatTcp::~PlatTcp(void) +{ + for (Listener &listener : mListeners) + { + listener.Disable(); + } + + for (Connection &connection : mConnections) + { + connection.Abort(); + } + + HandleListenerTask(); + HandleConnectionTask(); + + mListenerTask.Unpost(); + mConnectionTask.Unpost(); +} + +void PlatTcp::HandleListenerTask(void) +{ + LinkedList disabledListeners; + Listener *listener; + + mListeners.RemoveAllMatching(disabledListeners, Listener::kStateDisabled); + + while ((listener = disabledListeners.Pop()) != nullptr) + { + listener->mNext = nullptr; + listener->SetState(Listener::kStateUnused); + } +} + +void PlatTcp::HandleConnectionTask(void) +{ + LinkedList disconnectedConnections; + Connection *connection; + + mConnections.RemoveAllMatching(disconnectedConnections, Connection::kStateDisconnected); + + while ((connection = disconnectedConnections.Pop()) != nullptr) + { + connection->mNext = nullptr; + connection->SetState(Connection::kStateUnused); + } +} + +PlatTcp::Listener *PlatTcp::IterateListeners(Listener *aPrev) +{ + Listener *listener = (aPrev == nullptr) ? mListeners.GetHead() : aPrev->GetNext(); + + while (listener != nullptr) + { + switch (listener->GetState()) + { + case Listener::kStateUnused: + case Listener::kStateDisabled: + break; + case Listener::kStateEnabled: + ExitNow(); + } + + listener = listener->GetNext(); + } + +exit: + return listener; +} + +PlatTcp::Connection *PlatTcp::IterateConnections(Connection *aPrev) +{ + Connection *connection = (aPrev == nullptr) ? mConnections.GetHead() : aPrev->GetNext(); + + while (connection != nullptr) + { + switch (connection->GetState()) + { + case Connection::kStateUnused: + case Connection::kStateDisconnected: + break; + case Connection::kStateConnecting: + case Connection::kStateConnected: + case Connection::kStateToClose: + case Connection::kStateClosing: + ExitNow(); + } + + connection = connection->GetNext(); + } + +exit: + return connection; +} + +} // namespace Ip6 +} // namespace ot + +#endif // OPENTHREAD_CONFIG_PLATFORM_TCP_ENABLE diff --git a/src/core/net/plat_tcp.hpp b/src/core/net/plat_tcp.hpp new file mode 100644 index 000000000..8770d6ba6 --- /dev/null +++ b/src/core/net/plat_tcp.hpp @@ -0,0 +1,615 @@ +/* + * Copyright (c) 2026, 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 includes definitions for platform TCP. + */ + +#ifndef OT_CORE_NET_PLAT_TCP_HPP_ +#define OT_CORE_NET_PLAT_TCP_HPP_ + +#include "openthread-core-config.h" + +#if OPENTHREAD_CONFIG_PLATFORM_TCP_ENABLE + +#include + +#include "common/as_core_type.hpp" +#include "common/clearable.hpp" +#include "common/linked_list.hpp" +#include "common/locator.hpp" +#include "common/message.hpp" +#include "common/non_copyable.hpp" +#include "common/owned_ptr.hpp" +#include "common/tasklet.hpp" +#include "net/ip6_address.hpp" +#include "net/socket.hpp" + +namespace ot { +namespace Ip6 { + +/** + * @addtogroup core-tcp + * + * @brief + * This module includes definitions for platform TCP manager. + * + * @{ + */ + +extern "C" otPlatTcpConnection *otPlatTcpAccept(otPlatTcpListener *aListener, const otPlatTcpSockAddr *aPeerSockAddr); + +extern "C" void otPlatTcpHandleConnected(otPlatTcpConnection *aConn); +extern "C" void otPlatTcpHandleTxReady(otPlatTcpConnection *aConn); +extern "C" void otPlatTcpHandleReceive(otPlatTcpConnection *aConn, const uint8_t *aBuffer, uint16_t aLength); +extern "C" void otPlatTcpHandleDisconnected(otPlatTcpConnection *aConn, otPlatTcpDisconnectReason aReason); + +/** + * Represents the platform TCP manager. + */ +class PlatTcp : public InstanceLocator, private NonCopyable +{ +public: + class Connection; + + /** + * Defines the reason for a TCP connection disconnection. + */ + enum DisconnectReason : uint8_t + { + kDisconnectReasonClosed = OT_PLAT_TCP_DISCONNECT_REASON_CLOSED, ///< Closed + kDisconnectReasonTimeout = OT_PLAT_TCP_DISCONNECT_REASON_TIMEOUT, ///< Timed out + kDisconnectReasonRefused = OT_PLAT_TCP_DISCONNECT_REASON_REFUSED, ///< Refused by the peer + kDisconnectReasonReset = OT_PLAT_TCP_DISCONNECT_REASON_RESET, ///< Reset by the peer + kDisconnectReasonError = OT_PLAT_TCP_DISCONNECT_REASON_ERROR, ///< Other errors + kDisconnectReasonAbort, ///< Aborted + kDisconnectReasonNoBufs, ///< Buffer allocation fail. + }; + + /** + * Represents a TCP socket address. + */ + class SockAddr : public otPlatTcpSockAddr, public Clearable, public Equatable + { + public: + static constexpr uint16_t kInfoStringSize = 80; ///< String size used by `ToString()` + + /** + * Defines the fixed-length `String` object returned from `ToString()`. + */ + typedef String InfoString; + + /** + * Initializes the socket address (all fields are cleared). + */ + SockAddr(void) { Clear(); } + + /** + * Gets the port number. + * + * @returns The port number. + */ + uint16_t GetPort(void) const { return mSockAddr.mPort; } + + /** + * Gets the IP address. + * + * @returns The IP address. + */ + const Address &GetAddress(void) const { return AsCoreType(&mSockAddr.mAddress); } + + /** + * Gets the interface index. + * + * @returns The interface index. + */ + uint32_t GetIfIndex(void) const { return mIfIndex; } + + /** + * Gets the IP address. + * + * @returns A reference to the IP address. + */ + Address &GetAddress(void) { return AsCoreType(&mSockAddr.mAddress); } + + /** + * Sets the IP address. + * + * @param[in] aAddress The IP address. + */ + void SetAddress(const Address &aAddress) { mSockAddr.mAddress = aAddress; } + + /** + * Sets the port number. + * + * @param[in] aPort The port number. + */ + void SetPort(uint16_t aPort) { mSockAddr.mPort = aPort; } + + /** + * Sets the interface index. + * + * @param[in] aIfIndex The interface index. + */ + void SetIfIndex(uint32_t aIfIndex) { mIfIndex = aIfIndex; } + + /** + * Indicates whether all fields are unspecified (zero). + * + * @retval TRUE All fields are unspecified. + * @retval FALSE Not all fields are unspecified. + */ + bool IsAllUnspecified(void) const; + + /** + * Converts the SockAddr to human-readbale string + * + * The string is formatted as "[%]:". + * + * @returns An `InfoString` representation of the SockAddr + */ + InfoString ToString(void) const; + }; + + /** + * Represents a TCP listener. + */ + class Listener : public otPlatTcpListener, + public InstanceLocator, + public LinkedListEntry, + private NonCopyable + { + friend class PlatTcp; + friend class LinkedList; + friend class LinkedListEntry; + friend otPlatTcpConnection *otPlatTcpAccept(otPlatTcpListener *aListener, + const otPlatTcpSockAddr *aPeerSockAddr); + + public: + /** + * Defines the state of the listener. + */ + enum State : uint8_t + { + kStateUnused, ///< Listener is unused and not tracked by `PlatTcp`. + kStateEnabled, ///< Listener is enabled. + kStateDisabled, ///< Listener is disabled. + }; + + /** + * Defines the accept handler callback. + * + * This callback is invoked when an incoming connection request is received by the listener. + * + * To accept the connection, the handler should return a pointer to a `Connection` instance. The ownership + * of this `Connection` is then transferred to the `PlatTcp` module. To reject the incoming connection + * request, the handler should return `nullptr`. + * + * If the returned `Connection` instance is in the unused or disconnected state, it will be used to accept + * the request. If the instance is already in-use, the connection request is automatically rejected. This + * behavior simplifies the callback implementation. For example, an implementation managing a single + * `Connection` instance can simply return it, knowing it will be safely used only if it is available. + * + * @param[in] aListener The listener receiving the connection request. + * @param[in] aPeerSockAddr The peer's socket address. + * + * @returns A pointer to a `Connection` to accept the request, or `nullptr` to reject it. + */ + typedef Connection *(*AcceptHandler)(Listener &aListener, const SockAddr &aPeerSockAddr); + + /** + * Defines the delete handler callback. + * + * This callback notifies the caller that the listener instance is no longer being tracked or used by the + * `PlatTcp` module and can be safely reclaimed or deallocated. + * + * When a listener is enabled (via `Enable()`), its ownership is effectively transferred to the `PlatTcp` + * module. Once it is disabled and all internal cleanup is complete, the `PlatTcp` module invokes this + * callback to return ownership to the caller. + */ + typedef void (*DeleteHandler)(Listener &aListener); + + /** + * Initializes a new listener. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aAcceptHandler The accept handler callback. Can be `nullptr` if not needed. + * @param[in] aDeleteHandler The delete handler callback. Can be `nullptr` if not needed. + */ + Listener(Instance &aInstance, AcceptHandler aAcceptHandler, DeleteHandler aDeleteHandler); + + /** + * Gets the current state of the listener. + * + * @returns The state of the listener. + */ + State GetState(void) const { return mState; } + + /** + * Enables the listener. + * + * Calling this method transfers the ownership of the `Listener` instance to the `PlatTcp` module, + * which will track and manage it. The caller must not reclaim or deallocate the instance until the + * `DeleteHandler` callback is invoked, regardless of the error returned by this method. + * + * @param[in] aLocalSockAddr The local socket address to listen on. + * + * @retval kErrorNone Successfully enabled the listener. + * @retval kErrorAlready Already listening on the same port/address. + * @retval kErrorFailed Failed to enable the listener. + */ + Error Enable(const SockAddr &aLocalSockAddr); + + /** + * Disables the listener. + */ + void Disable(void); + + private: + bool Matches(State aState) const { return mState == aState; } + void SetState(State aState); + Connection *Accept(const SockAddr &aPeerSockAddr); + + Listener *mNext; + State mState; + SockAddr mLocalSockAddr; + AcceptHandler mAcceptHandler; + DeleteHandler mDeleteHandler; + }; + + /** + * Represents a TCP connection. + */ + class Connection : public otPlatTcpConnection, + public InstanceLocator, + public LinkedListEntry, + private NonCopyable + { + friend class PlatTcp; + friend class LinkedList; + friend class LinkedListEntry; + + friend void otPlatTcpHandleConnected(otPlatTcpConnection *aConn); + friend void otPlatTcpHandleTxReady(otPlatTcpConnection *aConn); + friend void otPlatTcpHandleReceive(otPlatTcpConnection *aConn, const uint8_t *aBuffer, uint16_t aLength); + friend void otPlatTcpHandleDisconnected(otPlatTcpConnection *aConn, otPlatTcpDisconnectReason aReason); + + public: + /** + * Defines the state of the connection. + */ + enum State : uint8_t + { + kStateUnused, ///< Unused and not tracked by `PlatTcp`. + kStateConnecting, ///< Connecting. + kStateConnected, ///< Connected. + kStateToClose, ///< To close (after sending queued message). + kStateClosing, ///< Closing. + kStateDisconnected, ///< Disconnected. + }; + + /** + * Represents connection events. + */ + enum Event : uint8_t + { + kEventConnected, ///< Connection established. + kEventDisconnected, ///< Connection disconnected or failed to connect. + kEventSendDone, ///< TX queue is empty. + kEventReceive, ///< Data was received and is available to read. + }; + + /** + * Defines the event handler callback. + */ + typedef void (*EventHandler)(Connection &aConnection, Event aEvent); + + /** + * Defines the delete handler callback. + * + * This callback notifies the caller that the connection instance is no longer being tracked or used by the + * `PlatTcp` module and can be safely reclaimed or deallocated. + * + * When a connection is initiated (via `Connect()` or `BindAndConnect()`), or when an incoming connection is + * accepted, its ownership is effectively transferred to the `PlatTcp` module. Once the connection is fully + * disconnected or aborted and all internal cleanup is complete, the `PlatTcp` module invokes this callback + * to return ownership to the caller. + */ + typedef void (*DeleteHandler)(Connection &aConnection); + + /** + * Initializes a new connection. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aEventHandler The event handler callback. Can be `nullptr` if not needed. + * @param[in] aDeleteHandler The delete handler callback. Can be `nullptr` if not needed. + */ + Connection(Instance &aInstance, EventHandler aEventHandler, DeleteHandler aDeleteHandler); + + /** + * Gets the current state of the connection. + * + * @returns The state of the connection. + */ + State GetState(void) const { return mState; } + + /** + * Initiates a TCP connection to a peer. + * + * Calling this method transfers the ownership of the `Connection` instance to the `PlatTcp` module, + * which will track and manage it. The caller must not reclaim or deallocate the instance until the + * `DeleteHandler` callback is invoked, regardless of the error returned by this method. + * + * If the connection is successfully initiated, the `EventHandler` callback will be invoked later with + * `kEventConnected` when the connection is established, or `kEventDisconnected` if it fails to connect. + * + * @param[in] aPeerSockAddr The peer's socket address. + * + * @retval kErrorNone Successfully initiated the connection. + * @retval kErrorInvalidState The connection is not in a valid state to initiate a connection. + * @retval kErrorFailed Failed to initiate the connection. + */ + Error Connect(const SockAddr &aPeerSockAddr); + + /** + * Initiates a TCP connection to a peer, specifying the local address. + * + * Calling this method transfers the ownership of the `Connection` instance to the `PlatTcp` module, + * which will track and manage it. The caller must not reclaim or deallocate the instance until the + * `DeleteHandler` callback is invoked, regardless of the error returned by this method. + * + * If the connection is successfully initiated, the `EventHandler` callback will be invoked later with + * `kEventConnected` when the connection is established, or `kEventDisconnected` if it fails to connect. + * + * The `aLocalSockAddr` specifies the local address to bind to. If the port or address in `aLocalSockAddr` + * are left unspecified, the platform will dynamically select the appropriate local port and/or address. + * + * @param[in] aLocalSockAddr The local socket address. + * @param[in] aPeerSockAddr The peer's socket address. + * + * @retval kErrorNone Successfully initiated the connection. + * @retval kErrorInvalidState The connection is not in a valid state to initiate a connection. + * @retval kErrorFailed Failed to initiate the connection. + */ + Error BindAndConnect(const SockAddr &aLocalSockAddr, const SockAddr &aPeerSockAddr); + + /** + * Sends data over the TCP connection. + * + * This method enqueues the provided @p aMessage into the connection's transmit queue. It should be called + * when the connection is in the `kStateConnected` state. + * + * The `Connection` implementation handles passing this data to the platform by notifying it of pending + * data and providing the data in chunks whenever the platform indicates readiness for transmission. + * + * Ownership of the @p aMessage is always transferred to this method, regardless of the error returned. + * + * To manage flow control, the `EventHandler` callback will be invoked with the `kEventSendDone` event once + * all previously queued data (from this and all prior `Send()` calls) has been successfully passed to the + * platform for transmission. This event is not per-message but serves as a stream-level notification that + * the connection's transmit queue is now empty. Higher layers can use this event to pace traffic generation. + * + * @param[in] aMessage The message containing the data to send. The ownership is transferred. + * + * @retval kErrorNone Successfully queued the data for transmission. + * @retval kErrorInvalidState Connection is not in connected state. + */ + Error Send(OwnedPtr aMessage); + + /** + * Indicates whether there is pending data for transmission. + * + * @retval TRUE There is pending transmit data. + * @retval FALSE There is no pending transmit data. + */ + bool IsTxPending(void) const { return !mTxQueue.IsEmpty(); } + + /** + * Gets the received message. + * + * This message contains all the data received over the connection that has not yet been removed or freed. + * It may include both newly arrived data and previously received data that has not yet been parsed by the + * caller. The `GetOffset()` of the message points to the beginning of the latest received chunk of data. + * + * Callers are notified of new received data via the `kEventReceive` event. Upon receiving this event, they can + * use this method to access the received message, parse the desired data from it, and then use + * `RemoveParsedLengthFromRxMessage()` to remove the processed portion while keeping any unparsed data in the + * `RxMessage`. Alternatively, `FreeRxMessage()` can be used to clear all received content. + * + * @returns A pointer to the received message, or `nullptr` if there is no received data. + */ + const Message *GetRxMessage(void) const { return mRxMessage; } + + /** + * Removes a specified number of parsed bytes from the received message. + * + * This method should be called after parsing/processing data from the received message to free up the + * buffer space. It adjusts the message length accordingly. + * + * It is recommended to parse all available information from `GetRxMessage()` first and then call this method + * once with the total number of bytes parsed. Since this method can involve data movement operations, + * batching the removal of parsed data is more efficient than calling it for each small parsed piece. + * + * @param[in] aRemoveLength The number of bytes to remove from the beginning of `GetRxMessage()`. + */ + void RemoveParsedLengthFromRxMessage(uint16_t aRemoveLength); + + /** + * Frees the received message and clears all previously received data. + * + * This method is useful to discard all received data and free the associated `Message` instance. + * + * Calling this method invalidates the `Message` pointer previously obtained from `GetRxMessage()`, and it must + * no longer be used. + */ + void FreeRxMessage(void); + + /** + * Gracefully closes the TCP connection. + * + * This method can be called even if there are still pending messages to be sent. The implementation + * ensures that all previously queued transmit data is passed to the platform for transmission before + * initiating the connection closure. + * + * If the connection is already in the process of closing or is disconnected, this call is ignored. + * + * Once the connection is fully disconnected (or if an error occurs during the close operation), + * the `EventHandler` callback will be invoked with the `kEventDisconnected` event. + */ + void Close(void); + + /** + * Aborts the TCP connection. + * + * This forceful termination discards any unsent data. After this call, the connection is immediately moved + * to the disconnected state and no further events (including `kEventDisconnected`) will be emitted to + * the `EventHandler` callback. + */ + void Abort(void); + + /** + * Gets the disconnect reason. + * + * @returns The disconnect reason. + */ + DisconnectReason GetDisconnectReason(void) const { return mDisconnectReason; } + + /** + * Converts a connection event to a string. + * + * @param[in] aEvent A connection event. + * + * @returns A string representation of @p aEvent. + */ + static const char *EventToString(Event aEvent); + + private: + bool Matches(State aState) const { return mState == aState; } + void SetState(State aState); + Error Prepare(const SockAddr &aLocalSockAddr, const SockAddr &aPeerSockAddr); + void HandleConnected(void); + void HandleTxReady(void); + void HandleReceive(const uint8_t *aBuffer, uint16_t aLength); + void HandleDisconnected(DisconnectReason aReason); + void SignalEvent(Event aEvent); + + Connection *mNext; + State mState; + DisconnectReason mDisconnectReason; + SockAddr mLocalSockAddr; + SockAddr mPeerSockAddr; + Message *mRxMessage; + MessageQueue mTxQueue; + EventHandler mEventHandler; + DeleteHandler mDeleteHandler; + }; + + /** + * Initializes the platform TCP manager. + * + * @param[in] aInstance The OpenThread instance. + */ + explicit PlatTcp(Instance &aInstance); + + /** + * Destructor for the platform TCP manager. + * + * Disables all active listeners and aborts all active connections upon destruction. + */ + ~PlatTcp(void); + + /** + * Returns the list of active TCP listeners. + * + * @returns A reference to the list of active TCP listeners. + */ + LinkedList &GetListeners(void) { return mListeners; } + + /** + * Returns the list of active TCP connections. + * + * @returns A reference to the list of active TCP connections. + */ + LinkedList &GetConnections(void) { return mConnections; } + + /** + * Iterates over the active TCP listeners. + * + * This method iterates over all listeners skipping those that are in `kStateUnused` or `kStateDisabled` state. + * + * @param[in] aPrev A pointer to the previous listener, or `nullptr` to start from the beginning. + * + * @returns A pointer to the next active listener, or `nullptr` if no more active listeners are found. + */ + Listener *IterateListeners(Listener *aPrev); + + /** + * Iterates over the active TCP connections. + * + * This method iterates over all connections skipping those that are in `kStateUnused` or `kStateDisconnected` + * state. + * + * @param[in] aPrev A pointer to the previous connection, or `nullptr` to start from the beginning. + * + * @returns A pointer to the next active connection, or `nullptr` if no more active connections are found. + */ + Connection *IterateConnections(Connection *aPrev); + +private: + void AddListener(Listener &aListener) { mListeners.Push(aListener); } + void AddConnection(Connection &aConnection) { mConnections.Push(aConnection); } + void PostListenerTask(void) { mListenerTask.Post(); } + void PostConnectionTask(void) { mConnectionTask.Post(); } + void HandleListenerTask(void); + void HandleConnectionTask(void); + + using ListenerTask = TaskletIn; + using ConnectionTask = TaskletIn; + + LinkedList mListeners; + LinkedList mConnections; + ListenerTask mListenerTask; + ConnectionTask mConnectionTask; +}; + +/** + * @} + */ + +} // namespace Ip6 + +DefineCoreType(otPlatTcpSockAddr, Ip6::PlatTcp::SockAddr); +DefineCoreType(otPlatTcpListener, Ip6::PlatTcp::Listener); +DefineCoreType(otPlatTcpConnection, Ip6::PlatTcp::Connection); + +} // namespace ot + +#endif // OPENTHREAD_CONFIG_PLATFORM_TCP_ENABLE + +#endif // OT_CORE_NET_PLAT_TCP_HPP_ diff --git a/tests/toranj/openthread-core-toranj-config-simulation.h b/tests/toranj/openthread-core-toranj-config-simulation.h index 7adc71913..5ec453d02 100644 --- a/tests/toranj/openthread-core-toranj-config-simulation.h +++ b/tests/toranj/openthread-core-toranj-config-simulation.h @@ -101,6 +101,8 @@ #define OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE +#define OPENTHREAD_CONFIG_PLATFORM_TCP_ENABLE 1 + #define OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE 1 #define OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE 1 diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 70f49b1f1..35d6adb16 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -248,6 +248,7 @@ ot_unit_test(network_data) ot_unit_test(network_name) ot_unit_test(offset_range) ot_unit_test(pool) +ot_unit_test(plat_tcp) ot_unit_test(power_calibration) ot_unit_test(priority_queue) ot_unit_test(pskc) diff --git a/tests/unit/test_plat_tcp.cpp b/tests/unit/test_plat_tcp.cpp new file mode 100644 index 000000000..f844f2f38 --- /dev/null +++ b/tests/unit/test_plat_tcp.cpp @@ -0,0 +1,733 @@ +/* + * Copyright (c) 2026, 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. + */ + +#include + +#include "test_platform.h" +#include "test_util.hpp" + +#include "common/arg_macros.hpp" +#include "common/as_core_type.hpp" +#include "instance/instance.hpp" +#include "net/plat_tcp.hpp" + +namespace ot { + +#if OPENTHREAD_CONFIG_PLATFORM_TCP_ENABLE + +// Logs a message +#define Log(...) printf(OT_FIRST_ARG(__VA_ARGS__) "\n" OT_REST_ARGS(__VA_ARGS__)) + +using SockAddr = Ip6::PlatTcp::SockAddr; +using Listener = Ip6::PlatTcp::Listener; +using Connection = Ip6::PlatTcp::Connection; + +struct ListenerInfo +{ + Listener *mListener; + SockAddr mLocalSockAddr; + bool mEnabled; +}; + +static constexpr uint16_t kTxBufferSize = 1000; + +struct ConnInfo +{ + Connection *mConnection; + bool mConnected; + bool mConnecting; + bool mClosing; + bool mAborted; + bool mNotifyTxPending; + SockAddr mLocalSockAddr; + SockAddr mPeerSockAddr; + uint16_t mTxBufferUsed; + uint8_t mTxBuffer[kTxBufferSize]; +}; + +static ListenerInfo sListenerInfo; +static ConnInfo sConnInfo; + +static bool sAcceptHandlerCalled; +static bool sDeleteHandlerCalled; +static bool sEventHandlerCalled; +static Connection::Event sLastEvent; + +static void ResetTestState(void) +{ + ClearAllBytes(sListenerInfo); + ClearAllBytes(sConnInfo); + + sAcceptHandlerCalled = false; + sDeleteHandlerCalled = false; + sEventHandlerCalled = false; +} + +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// otPlatTcp + +extern "C" { + +otError otPlatTcpEnableListener(otPlatTcpListener *aListener, const otPlatTcpSockAddr *aLocalSockAddr) +{ + sListenerInfo.mListener = AsCoreTypePtr(aListener); + sListenerInfo.mLocalSockAddr = AsCoreType(aLocalSockAddr); + sListenerInfo.mEnabled = true; + + Log(" - otPlatTcpEnableListener(%s)", sListenerInfo.mLocalSockAddr.ToString().AsCString()); + + return kErrorNone; +} + +void otPlatTcpDisableListener(otPlatTcpListener *aListener) +{ + Log(" - otPlatTcpDisableListener()"); + + VerifyOrQuit(sListenerInfo.mListener == aListener); + sListenerInfo.mEnabled = false; +} + +otError otPlatTcpConnect(otPlatTcpConnection *aConn, + const otPlatTcpSockAddr *aPeerSockAddr, + const otPlatTcpSockAddr *aLocalSockAddr) +{ + sConnInfo.mConnection = AsCoreTypePtr(aConn); + sConnInfo.mConnecting = true; + sConnInfo.mPeerSockAddr = AsCoreType(aPeerSockAddr); + + Log(" - otPlatTcpConnect(peerAddr:%s)", sConnInfo.mPeerSockAddr.ToString().AsCString()); + + if (aLocalSockAddr != nullptr) + { + sConnInfo.mLocalSockAddr = AsCoreType(aLocalSockAddr); + Log(" localAddr:%s", sConnInfo.mLocalSockAddr.ToString().AsCString()); + } + + return kErrorNone; +} + +void otPlatTcpNotifyTxPending(otPlatTcpConnection *aConn) +{ + Log(" - otPlatTcpNotifyTxPending()"); + VerifyOrQuit(sConnInfo.mConnection == aConn); + sConnInfo.mNotifyTxPending = true; +} + +uint16_t otPlatTcpSend(otPlatTcpConnection *aConn, const uint8_t *aBuffer, uint16_t aLength) +{ + uint16_t copyLength; + + VerifyOrQuit(sConnInfo.mConnection == aConn); + + copyLength = Min(aLength, kTxBufferSize - sConnInfo.mTxBufferUsed); + + memcpy(&sConnInfo.mTxBuffer[sConnInfo.mTxBufferUsed], aBuffer, copyLength); + sConnInfo.mTxBufferUsed += copyLength; + + Log(" - otPlatTcpSend(aLength:%u), copied:%u", aLength, copyLength); + + return copyLength; +} + +void otPlatTcpClose(otPlatTcpConnection *aConn) +{ + Log(" - otPlatTcpClose()"); + + VerifyOrQuit(sConnInfo.mConnection == aConn); + sConnInfo.mClosing = true; +} + +void otPlatTcpAbort(otPlatTcpConnection *aConn) +{ + Log(" - otPlatTcpAbort()"); + + VerifyOrQuit(sConnInfo.mConnection == aConn); + sConnInfo.mAborted = true; +} + +} // extern "C" + +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// Callbacks + +static Connection *AcceptHandler(Listener &aListener, const SockAddr &aPeerSockAddr) +{ + Log(" - AcceptHandler(peerAddr:%s)", aPeerSockAddr.ToString().AsCString()); + + OT_UNUSED_VARIABLE(aListener); + sAcceptHandlerCalled = true; + + return sConnInfo.mConnection; +} + +static void ListenerDeleteHandler(Listener &aListener) +{ + Log(" - ListenerDeleteHandler()"); + + sDeleteHandlerCalled = true; + OT_UNUSED_VARIABLE(aListener); +} + +static void ConnectionDeleteHandler(Connection &aConnection) +{ + Log(" - ConnectionDeleteHandler()"); + + sDeleteHandlerCalled = true; + OT_UNUSED_VARIABLE(aConnection); +} + +static void EventHandler(Connection &aConnection, Connection::Event aEvent) +{ + Log(" - EventHandler(%s)", Connection::EventToString(aEvent)); + + sLastEvent = aEvent; + sEventHandlerCalled = true; + OT_UNUSED_VARIABLE(aConnection); +} + +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +void TestPlatTcpListener(void) +{ + Instance *instance = testInitInstance(); + Listener listener(*instance, AcceptHandler, ListenerDeleteHandler); + SockAddr localAddr; + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("TestPlatTcpListener"); + + ResetTestState(); + + localAddr.SetPort(1234); + + Log(" Enable listener"); + SuccessOrQuit(listener.Enable(localAddr)); + VerifyOrQuit(listener.GetState() == Listener::kStateEnabled); + VerifyOrQuit(sListenerInfo.mEnabled); + VerifyOrQuit(sListenerInfo.mLocalSockAddr == localAddr); + + Log(" Check active listeners"); + VerifyOrQuit(instance->Get().GetListeners().GetHead() == &listener); + VerifyOrQuit(listener.GetNext() == nullptr); + + Log(" Disable listener"); + sDeleteHandlerCalled = false; + listener.Disable(); + VerifyOrQuit(listener.GetState() == Listener::kStateDisabled); + VerifyOrQuit(!sListenerInfo.mEnabled); + + Log(" Verify delete handler is called"); + VerifyOrQuit(!sDeleteHandlerCalled); + otTaskletsProcess(instance); + VerifyOrQuit(sDeleteHandlerCalled); + VerifyOrQuit(listener.GetState() == Listener::kStateUnused); + + Log(" Check active listeners list is empty"); + VerifyOrQuit(instance->Get().GetListeners().IsEmpty()); + + testFreeInstance(instance); + + Log("End of TestPlatTcpListener"); +} + +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +void TestPlatTcpConnection(void) +{ + Instance *instance = testInitInstance(); + Connection connection(*instance, EventHandler, ConnectionDeleteHandler); + SockAddr peerAddr; + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("TestPlatTcpConnection"); + + ResetTestState(); + + SuccessOrQuit(peerAddr.GetAddress().FromString("fd00:1234::cafe")); + peerAddr.SetPort(5678); + peerAddr.SetIfIndex(4); + + VerifyOrQuit(instance->Get().GetConnections().IsEmpty()); + + Log(" Connect"); + sEventHandlerCalled = false; + SuccessOrQuit(connection.Connect(peerAddr)); + VerifyOrQuit(connection.GetState() == Connection::kStateConnecting); + VerifyOrQuit(sConnInfo.mConnecting); + VerifyOrQuit(!sEventHandlerCalled); + + VerifyOrQuit(instance->Get().GetConnections().GetHead() == &connection); + VerifyOrQuit(connection.GetNext() == nullptr); + + Log(" Signal connected `otPlatTcpHandleConnected`"); + otPlatTcpHandleConnected(&connection); + VerifyOrQuit(sEventHandlerCalled); + VerifyOrQuit(sLastEvent == Connection::kEventConnected); + VerifyOrQuit(connection.GetState() == Connection::kStateConnected); + + Log(" Disconnect from platform `otPlatTcpHandleDisconnected`"); + sEventHandlerCalled = false; + otPlatTcpHandleDisconnected(&connection, OT_PLAT_TCP_DISCONNECT_REASON_CLOSED); + VerifyOrQuit(sEventHandlerCalled); + VerifyOrQuit(sLastEvent == Connection::kEventDisconnected); + VerifyOrQuit(connection.GetState() == Connection::kStateDisconnected); + VerifyOrQuit(connection.GetDisconnectReason() == Ip6::PlatTcp::kDisconnectReasonClosed); + + VerifyOrQuit(instance->Get().GetConnections().GetHead() == &connection); + VerifyOrQuit(connection.GetNext() == nullptr); + + Log(" Cleanup"); + sDeleteHandlerCalled = false; + otTaskletsProcess(instance); + VerifyOrQuit(sDeleteHandlerCalled); + VerifyOrQuit(connection.GetState() == Connection::kStateUnused); + + VerifyOrQuit(instance->Get().GetConnections().IsEmpty()); + + testFreeInstance(instance); + + Log("End of TestPlatTcpConnection"); +} + +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +void TestPlatTcpSendRecv(void) +{ + static const uint8_t kData[] = {1, 2, 3, 4, 5, 6, 7}; + static const uint8_t kData2[] = {0xfe, 0xdc, 0xba}; + + Instance *instance = testInitInstance(); + Connection connection(*instance, EventHandler, ConnectionDeleteHandler); + SockAddr localAddr; + SockAddr peerAddr; + OwnedPtr message; + const Message *rxMessage; + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("TestPlatTcpSendRecv"); + + ResetTestState(); + sConnInfo.mConnection = &connection; + + SuccessOrQuit(localAddr.GetAddress().FromString("fd00:beef:cafe::2")); + localAddr.SetPort(4321); + localAddr.SetIfIndex(7); + + SuccessOrQuit(peerAddr.GetAddress().FromString("fd00:beef:cafe::1")); + peerAddr.SetPort(5678); + peerAddr.SetIfIndex(7); + + Log(" Connect"); + + SuccessOrQuit(connection.BindAndConnect(localAddr, peerAddr)); + otPlatTcpHandleConnected(&connection); + VerifyOrQuit(connection.GetState() == Connection::kStateConnected); + + VerifyOrQuit(sConnInfo.mConnection == &connection); + VerifyOrQuit(sConnInfo.mLocalSockAddr == localAddr); + VerifyOrQuit(sConnInfo.mPeerSockAddr == peerAddr); + + Log(" Send data"); + + message.Reset(instance->Get().Allocate(Message::kTypeOther)); + SuccessOrQuit(message->AppendBytes(kData, sizeof(kData))); + + sEventHandlerCalled = false; + SuccessOrQuit(connection.Send(message.PassOwnership())); + VerifyOrQuit(!sEventHandlerCalled); + VerifyOrQuit(sConnInfo.mNotifyTxPending); + + Log(" Signal `otPlatTcpHandleTxReady`"); + otPlatTcpHandleTxReady(&connection); + VerifyOrQuit(sConnInfo.mTxBufferUsed == sizeof(kData)); + VerifyOrQuit(memcmp(sConnInfo.mTxBuffer, kData, sizeof(kData)) == 0); + + VerifyOrQuit(sEventHandlerCalled); + VerifyOrQuit(sLastEvent == Connection::kEventSendDone); + + Log(" Send more data"); + + message.Reset(instance->Get().Allocate(Message::kTypeOther)); + SuccessOrQuit(message->AppendBytes(kData2, sizeof(kData2))); + + sEventHandlerCalled = false; + SuccessOrQuit(connection.Send(message.PassOwnership())); + VerifyOrQuit(!sEventHandlerCalled); + VerifyOrQuit(sConnInfo.mNotifyTxPending); + + Log(" Signal `otPlatTcpHandleTxReady`"); + otPlatTcpHandleTxReady(&connection); + VerifyOrQuit(sConnInfo.mTxBufferUsed == sizeof(kData) + sizeof(kData2)); + VerifyOrQuit(memcmp(sConnInfo.mTxBuffer, kData, sizeof(kData)) == 0); + VerifyOrQuit(memcmp(&sConnInfo.mTxBuffer[sizeof(kData)], kData2, sizeof(kData2)) == 0); + + VerifyOrQuit(sEventHandlerCalled); + VerifyOrQuit(sLastEvent == Connection::kEventSendDone); + + Log(" Receive data `otPlatTcpHandleReceive`"); + sEventHandlerCalled = false; + otPlatTcpHandleReceive(&connection, kData, sizeof(kData)); + VerifyOrQuit(sEventHandlerCalled); + VerifyOrQuit(sLastEvent == Connection::kEventReceive); + + rxMessage = connection.GetRxMessage(); + VerifyOrQuit(rxMessage != nullptr); + VerifyOrQuit(rxMessage->GetLength() == sizeof(kData)); + VerifyOrQuit(rxMessage->CompareBytes(/* aOffset */ 0, kData, sizeof(kData))); + + Log(" Remove part of rx data"); + connection.RemoveParsedLengthFromRxMessage(2); + rxMessage = connection.GetRxMessage(); + VerifyOrQuit(rxMessage->GetLength() == sizeof(kData) - 2); + + Log(" Receive more data `otPlatTcpHandleReceive`"); + sEventHandlerCalled = false; + otPlatTcpHandleReceive(&connection, kData2, sizeof(kData2)); + VerifyOrQuit(sEventHandlerCalled); + VerifyOrQuit(sLastEvent == Connection::kEventReceive); + + rxMessage = connection.GetRxMessage(); + VerifyOrQuit(rxMessage != nullptr); + VerifyOrQuit(rxMessage->GetLength() == sizeof(kData) + sizeof(kData2) - 2); + VerifyOrQuit(rxMessage->CompareBytes(/* aOffset */ sizeof(kData) - 2, kData2, sizeof(kData2))); + VerifyOrQuit(rxMessage->GetOffset() == sizeof(kData) - 2); + + Log(" Free Rx message"); + connection.FreeRxMessage(); + VerifyOrQuit(connection.GetRxMessage() == nullptr); + + Log(" Close"); + sEventHandlerCalled = false; + connection.Close(); + VerifyOrQuit(sConnInfo.mClosing); + VerifyOrQuit(!sEventHandlerCalled); + + Log(" Signal `otPlatTcpHandleDisconnected`"); + otPlatTcpHandleDisconnected(&connection, OT_PLAT_TCP_DISCONNECT_REASON_CLOSED); + + VerifyOrQuit(sEventHandlerCalled); + VerifyOrQuit(sLastEvent == Connection::kEventDisconnected); + VerifyOrQuit(connection.GetState() == Connection::kStateDisconnected); + + Log(" Cleanup"); + sDeleteHandlerCalled = false; + otTaskletsProcess(instance); + VerifyOrQuit(sDeleteHandlerCalled); + VerifyOrQuit(connection.GetState() == Connection::kStateUnused); + + testFreeInstance(instance); + + Log("End of TestPlatTcpSendRecv"); +} + +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +void TestPlatTcpFlowControl(void) +{ + Instance *instance = testInitInstance(); + Connection connection(*instance, EventHandler, ConnectionDeleteHandler); + SockAddr peerAddr; + OwnedPtr message1, message2; + uint8_t data[600]; + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("TestPlatTcpFlowControl"); + + ResetTestState(); + + sConnInfo.mConnection = &connection; + + SuccessOrQuit(peerAddr.GetAddress().FromString("fd00:3f12::aaaa")); + peerAddr.SetPort(9876); + peerAddr.SetIfIndex(12); + + for (uint16_t i = 0; i < sizeof(data); i++) + { + data[i] = static_cast(i & 0xff) ^ static_cast(i >> 8); + } + + Log(" Connect"); + SuccessOrQuit(connection.Connect(peerAddr)); + otPlatTcpHandleConnected(&connection); + + Log(" Partial send (buffer full)"); + + message1.Reset(instance->Get().Allocate(Message::kTypeIp6)); + SuccessOrQuit(message1->AppendBytes(data, 600)); + message2.Reset(instance->Get().Allocate(Message::kTypeIp6)); + SuccessOrQuit(message2->AppendBytes(data, 600)); + + SuccessOrQuit(connection.Send(message1.PassOwnership())); + SuccessOrQuit(connection.Send(message2.PassOwnership())); + + Log(" Platform tx buffer size 1000. Validate partial write"); + + sEventHandlerCalled = false; + + Log(" Signal `otPlatTcpHandleTxReady`"); + otPlatTcpHandleTxReady(&connection); + + VerifyOrQuit(sConnInfo.mTxBufferUsed == kTxBufferSize); + VerifyOrQuit(memcmp(sConnInfo.mTxBuffer, data, sizeof(data)) == 0); + VerifyOrQuit(memcmp(&sConnInfo.mTxBuffer[sizeof(data)], data, kTxBufferSize - sizeof(data)) == 0); + + VerifyOrQuit(!sEventHandlerCalled, "SendDone should NOT be called yet"); + + Log(" Now clear platform tx buffer. Validate the rest of data is passed to platform"); + sConnInfo.mTxBufferUsed = 0; + + Log(" Signal `otPlatTcpHandleTxReady`"); + otPlatTcpHandleTxReady(&connection); + + VerifyOrQuit(sConnInfo.mTxBufferUsed == 2 * sizeof(data) - kTxBufferSize); + VerifyOrQuit(memcmp(sConnInfo.mTxBuffer, &data[sizeof(data) - sConnInfo.mTxBufferUsed], sConnInfo.mTxBufferUsed) == + 0); + + VerifyOrQuit(sEventHandlerCalled, "SendDone should be called now"); + VerifyOrQuit(sLastEvent == Connection::kEventSendDone); + + Log(" Abort"); + connection.Abort(); + VerifyOrQuit(connection.GetState() == Connection::kStateDisconnected); + VerifyOrQuit(sConnInfo.mAborted); + + testFreeInstance(instance); + Log("End of TestPlatTcpFlowControl"); +} + +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +void TestPlatTcpAccept(void) +{ + Instance *instance = testInitInstance(); + Listener listener(*instance, AcceptHandler, ListenerDeleteHandler); + Connection connection(*instance, EventHandler, ConnectionDeleteHandler); + Connection *acceptedConn; + SockAddr localAddr; + SockAddr peerAddr; + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("TestPlatTcpAccept"); + + ResetTestState(); + + sConnInfo.mConnection = &connection; + + localAddr.SetPort(1234); + + SuccessOrQuit(peerAddr.GetAddress().FromString("fe80::1234")); + peerAddr.SetPort(5678); + + Log(" Enable listener"); + SuccessOrQuit(listener.Enable(localAddr)); + + Log(" Signal incoming connection request `otPlatTcpAccept`"); + sAcceptHandlerCalled = false; + acceptedConn = AsCoreTypePtr(otPlatTcpAccept(&listener, &peerAddr)); + + VerifyOrQuit(sAcceptHandlerCalled); + VerifyOrQuit(acceptedConn == &connection); + VerifyOrQuit(connection.GetState() == Connection::kStateConnecting); + + Log(" Signal `otPlatTcpHandleConnected`"); + otPlatTcpHandleConnected(&connection); + VerifyOrQuit(connection.GetState() == Connection::kStateConnected); + + testFreeInstance(instance); + + Log("End of TestPlatTcpAccept"); +} + +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +void TestPlatTcpIteration(void) +{ + Instance *instance = testInitInstance(); + Listener listener1(*instance, AcceptHandler, ListenerDeleteHandler); + Listener listener2(*instance, AcceptHandler, ListenerDeleteHandler); + Connection connection1(*instance, EventHandler, ConnectionDeleteHandler); + Connection connection2(*instance, EventHandler, ConnectionDeleteHandler); + Listener *listener; + Connection *connection; + SockAddr sockAddr; + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("TestPlatTcpIteration"); + + ResetTestState(); + + Log(" Enable two listeners"); + + sockAddr.SetPort(1); + SuccessOrQuit(listener1.Enable(sockAddr)); + sockAddr.SetPort(2); + SuccessOrQuit(listener2.Enable(sockAddr)); + + VerifyOrQuit(instance->Get().GetListeners().GetHead() == &listener2); + VerifyOrQuit(listener2.GetNext() == &listener1); + VerifyOrQuit(listener1.GetNext() == nullptr); + + Log(" Validate IterateListeners"); + + listener = nullptr; + listener = instance->Get().IterateListeners(listener); + VerifyOrQuit(listener == &listener2); + listener = instance->Get().IterateListeners(listener); + VerifyOrQuit(listener == &listener1); + listener = instance->Get().IterateListeners(listener); + VerifyOrQuit(listener == nullptr); + + Log(" Disable first listener"); + + sListenerInfo.mListener = &listener1; + listener1.Disable(); + + Log(" Validate IterateListeners skips the disabled entry"); + VerifyOrQuit(instance->Get().GetListeners().GetHead() == &listener2); + VerifyOrQuit(listener2.GetNext() == &listener1); + VerifyOrQuit(listener1.GetNext() == nullptr); + + listener = nullptr; + listener = instance->Get().IterateListeners(listener); + VerifyOrQuit(listener == &listener2); + listener = instance->Get().IterateListeners(listener); + VerifyOrQuit(listener == nullptr); + + Log(" otTaskletsProcess"); + otTaskletsProcess(instance); + VerifyOrQuit(instance->Get().GetListeners().GetHead() == &listener2); + VerifyOrQuit(listener2.GetNext() == nullptr); + + Log(" Disable second listener"); + + VerifyOrQuit(instance->Get().GetListeners().GetHead() == &listener2); + VerifyOrQuit(listener2.GetNext() == nullptr); + + sListenerInfo.mListener = &listener2; + listener2.Disable(); + + listener = nullptr; + listener = instance->Get().IterateListeners(listener); + VerifyOrQuit(listener == nullptr); + + Log(" otTaskletsProcess"); + otTaskletsProcess(instance); + VerifyOrQuit(instance->Get().GetListeners().IsEmpty()); + + listener = nullptr; + listener = instance->Get().IterateListeners(listener); + VerifyOrQuit(listener == nullptr); + + Log(" Start two connections"); + + sockAddr.SetPort(3); + SuccessOrQuit(connection1.Connect(sockAddr)); + sockAddr.SetPort(4); + SuccessOrQuit(connection2.Connect(sockAddr)); + + VerifyOrQuit(instance->Get().GetConnections().GetHead() == &connection2); + VerifyOrQuit(connection2.GetNext() == &connection1); + VerifyOrQuit(connection1.GetNext() == nullptr); + + connection = nullptr; + connection = instance->Get().IterateConnections(connection); + VerifyOrQuit(connection == &connection2); + connection = instance->Get().IterateConnections(connection); + VerifyOrQuit(connection == &connection1); + connection = instance->Get().IterateConnections(connection); + VerifyOrQuit(connection == nullptr); + + Log(" Abort second connection"); + + sConnInfo.mConnection = &connection2; + connection2.Abort(); + + VerifyOrQuit(instance->Get().GetConnections().GetHead() == &connection2); + VerifyOrQuit(connection2.GetNext() == &connection1); + VerifyOrQuit(connection1.GetNext() == nullptr); + + connection = nullptr; + connection = instance->Get().IterateConnections(connection); + VerifyOrQuit(connection == &connection1); + connection = instance->Get().IterateConnections(connection); + VerifyOrQuit(connection == nullptr); + + Log(" otTaskletsProcess"); + otTaskletsProcess(instance); + VerifyOrQuit(instance->Get().GetConnections().GetHead() == &connection1); + VerifyOrQuit(connection1.GetNext() == nullptr); + + connection = nullptr; + connection = instance->Get().IterateConnections(connection); + VerifyOrQuit(connection == &connection1); + connection = instance->Get().IterateConnections(connection); + VerifyOrQuit(connection == nullptr); + + Log(" Abort first connection"); + + sConnInfo.mConnection = &connection1; + connection1.Abort(); + + VerifyOrQuit(instance->Get().GetConnections().GetHead() == &connection1); + VerifyOrQuit(connection1.GetNext() == nullptr); + + connection = nullptr; + connection = instance->Get().IterateConnections(connection); + VerifyOrQuit(connection == nullptr); + + Log(" otTaskletsProcess"); + otTaskletsProcess(instance); + VerifyOrQuit(instance->Get().GetConnections().IsEmpty()); + + connection = nullptr; + connection = instance->Get().IterateConnections(connection); + VerifyOrQuit(connection == nullptr); + + testFreeInstance(instance); + + Log("End of TestPlatTcpIteration"); +} + +#endif // OPENTHREAD_CONFIG_PLATFORM_TCP_ENABLE + +} // namespace ot + +int main(void) +{ +#if OPENTHREAD_CONFIG_PLATFORM_TCP_ENABLE + ot::TestPlatTcpListener(); + ot::TestPlatTcpConnection(); + ot::TestPlatTcpSendRecv(); + ot::TestPlatTcpFlowControl(); + ot::TestPlatTcpAccept(); + ot::TestPlatTcpIteration(); +#endif + + printf("All tests passed\n"); + return 0; +} diff --git a/tests/unit/test_platform.cpp b/tests/unit/test_platform.cpp index d81f4f319..4d0d8dbce 100644 --- a/tests/unit/test_platform.cpp +++ b/tests/unit/test_platform.cpp @@ -658,6 +658,46 @@ OT_TOOL_WEAK void otPlatMdnsSendUnicast(otInstance *aInstance, #endif // OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE +#if OPENTHREAD_CONFIG_PLATFORM_TCP_ENABLE + +OT_TOOL_WEAK otError otPlatTcpEnableListener(otPlatTcpListener *aListener, const otPlatTcpSockAddr *aLocalSockAddr) +{ + OT_UNUSED_VARIABLE(aListener); + OT_UNUSED_VARIABLE(aLocalSockAddr); + + return OT_ERROR_FAILED; +} + +OT_TOOL_WEAK void otPlatTcpDisableListener(otPlatTcpListener *aListener) { OT_UNUSED_VARIABLE(aListener); } + +OT_TOOL_WEAK otError otPlatTcpConnect(otPlatTcpConnection *aConn, + const otPlatTcpSockAddr *aPeerSockAddr, + const otPlatTcpSockAddr *aLocalSockAddr) +{ + OT_UNUSED_VARIABLE(aConn); + OT_UNUSED_VARIABLE(aPeerSockAddr); + OT_UNUSED_VARIABLE(aLocalSockAddr); + + return OT_ERROR_FAILED; +} + +OT_TOOL_WEAK void otPlatTcpNotifyTxPending(otPlatTcpConnection *aConn) { OT_UNUSED_VARIABLE(aConn); } + +OT_TOOL_WEAK uint16_t otPlatTcpSend(otPlatTcpConnection *aConn, const uint8_t *aBuffer, uint16_t aLength) +{ + OT_UNUSED_VARIABLE(aConn); + OT_UNUSED_VARIABLE(aBuffer); + OT_UNUSED_VARIABLE(aLength); + + return 0; +} + +OT_TOOL_WEAK void otPlatTcpClose(otPlatTcpConnection *aConn) { OT_UNUSED_VARIABLE(aConn); } + +OT_TOOL_WEAK void otPlatTcpAbort(otPlatTcpConnection *aConn) { OT_UNUSED_VARIABLE(aConn); } + +#endif // #if OPENTHREAD_CONFIG_PLATFORM_TCP_ENABLE + #if OPENTHREAD_CONFIG_DNS_DSO_ENABLE OT_TOOL_WEAK void otPlatDsoEnableListening(otInstance *aInstance, bool aEnable) diff --git a/tests/unit/test_platform.h b/tests/unit/test_platform.h index 87904c214..8ae275688 100644 --- a/tests/unit/test_platform.h +++ b/tests/unit/test_platform.h @@ -43,6 +43,7 @@ #include #include #include +#include #include #include "common/code_utils.hpp"