[platform] define otPlatTcp platform abstraction APIs (#13175)

This commit introduces a new platform abstraction layer for TCP
connections and listeners, enabling OpenThread to leverage platform-
provided TCP stacks. The API is designed for asynchronous, event-driven
environments and can easily support POSIX as well as various embedded
network stacks.

In the core, `Ip6::PlatTcp` and its nested `Connection` and `Listener`
classes are introduced to manage these platform interactions, providing
a clean C++ interface for the OpenThread core. The `PlatTcp` manager
is integrated into the `Instance` class and utilizes `Tasklet` for
asynchronous resource cleanup.

Additionally, this commit adds a comprehensive unit test to verify the
TCP platform abstraction and `Ip6::PlatTcp` implementation. The tests
cover listener and connection lifecycles, data flow, flow control,
incoming connection acceptance, and active object iteration.
This commit is contained in:
Abtin Keshavarzian
2026-06-10 10:23:06 -07:00
committed by GitHub
parent 1b80561802
commit a055c4b9b8
21 changed files with 2630 additions and 2 deletions
+1
View File
@@ -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
+1
View File
@@ -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")
@@ -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
+71
View File
@@ -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 <openthread/platform/tcp.h>
#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
+1
View File
@@ -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",
+1 -1
View File
@@ -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
+439
View File
@@ -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 <stdbool.h>
#include <stdint.h>
#include <openthread/error.h>
#include <openthread/instance.h>
#include <openthread/ip6.h>
#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_
+2
View File
@@ -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",
+1
View File
@@ -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
+13
View File
@@ -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<Message>
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.
*
+9
View File
@@ -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
*
+3
View File
@@ -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
+9
View File
@@ -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
+9 -1
View File
@@ -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);
+677
View File
@@ -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<PlatTcp::DisconnectReason>(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<PlatTcp>().IterateListeners(AsCoreTypePtr(aPrevListener));
}
extern "C" otPlatTcpConnection *otPlatTcpIterateConnections(otInstance *aInstance, otPlatTcpConnection *aPrevConn)
{
return AsCoreType(aInstance).Get<PlatTcp>().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<PlatTcp>().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<PlatTcp>().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<PlatTcp>().PostConnectionTask();
break;
}
exit:
return;
}
Error PlatTcp::Connection::Prepare(const SockAddr &aLocalSockAddr, const SockAddr &aPeerSockAddr)
{
Error error = kErrorNone;
switch (GetState())
{
case kStateUnused:
Get<PlatTcp>().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<Message> 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<MessagePool>().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<Listener> 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<Connection> 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
+615
View File
@@ -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 <openthread/platform/tcp.h>
#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<SockAddr>, public Equatable<SockAddr>
{
public:
static constexpr uint16_t kInfoStringSize = 80; ///< String size used by `ToString()`
/**
* Defines the fixed-length `String` object returned from `ToString()`.
*/
typedef String<kInfoStringSize> 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 "[<ipv6-address>%<if-index>]:<port>".
*
* @returns An `InfoString` representation of the SockAddr
*/
InfoString ToString(void) const;
};
/**
* Represents a TCP listener.
*/
class Listener : public otPlatTcpListener,
public InstanceLocator,
public LinkedListEntry<Listener>,
private NonCopyable
{
friend class PlatTcp;
friend class LinkedList<Listener>;
friend class LinkedListEntry<Listener>;
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<Connection>,
private NonCopyable
{
friend class PlatTcp;
friend class LinkedList<Connection>;
friend class LinkedListEntry<Connection>;
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<Message> 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<Listener> &GetListeners(void) { return mListeners; }
/**
* Returns the list of active TCP connections.
*
* @returns A reference to the list of active TCP connections.
*/
LinkedList<Connection> &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<PlatTcp, &PlatTcp::HandleListenerTask>;
using ConnectionTask = TaskletIn<PlatTcp, &PlatTcp::HandleConnectionTask>;
LinkedList<Listener> mListeners;
LinkedList<Connection> 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_
@@ -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
+1
View File
@@ -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)
+733
View File
@@ -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 <openthread/config.h>
#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<uint16_t>(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<Ip6::PlatTcp>().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<Ip6::PlatTcp>().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<Ip6::PlatTcp>().GetConnections().IsEmpty());
Log(" Connect");
sEventHandlerCalled = false;
SuccessOrQuit(connection.Connect(peerAddr));
VerifyOrQuit(connection.GetState() == Connection::kStateConnecting);
VerifyOrQuit(sConnInfo.mConnecting);
VerifyOrQuit(!sEventHandlerCalled);
VerifyOrQuit(instance->Get<Ip6::PlatTcp>().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<Ip6::PlatTcp>().GetConnections().GetHead() == &connection);
VerifyOrQuit(connection.GetNext() == nullptr);
Log(" Cleanup");
sDeleteHandlerCalled = false;
otTaskletsProcess(instance);
VerifyOrQuit(sDeleteHandlerCalled);
VerifyOrQuit(connection.GetState() == Connection::kStateUnused);
VerifyOrQuit(instance->Get<Ip6::PlatTcp>().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> 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<MessagePool>().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<MessagePool>().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<Message> 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<uint8_t>(i & 0xff) ^ static_cast<uint8_t>(i >> 8);
}
Log(" Connect");
SuccessOrQuit(connection.Connect(peerAddr));
otPlatTcpHandleConnected(&connection);
Log(" Partial send (buffer full)");
message1.Reset(instance->Get<MessagePool>().Allocate(Message::kTypeIp6));
SuccessOrQuit(message1->AppendBytes(data, 600));
message2.Reset(instance->Get<MessagePool>().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<Ip6::PlatTcp>().GetListeners().GetHead() == &listener2);
VerifyOrQuit(listener2.GetNext() == &listener1);
VerifyOrQuit(listener1.GetNext() == nullptr);
Log(" Validate IterateListeners");
listener = nullptr;
listener = instance->Get<Ip6::PlatTcp>().IterateListeners(listener);
VerifyOrQuit(listener == &listener2);
listener = instance->Get<Ip6::PlatTcp>().IterateListeners(listener);
VerifyOrQuit(listener == &listener1);
listener = instance->Get<Ip6::PlatTcp>().IterateListeners(listener);
VerifyOrQuit(listener == nullptr);
Log(" Disable first listener");
sListenerInfo.mListener = &listener1;
listener1.Disable();
Log(" Validate IterateListeners skips the disabled entry");
VerifyOrQuit(instance->Get<Ip6::PlatTcp>().GetListeners().GetHead() == &listener2);
VerifyOrQuit(listener2.GetNext() == &listener1);
VerifyOrQuit(listener1.GetNext() == nullptr);
listener = nullptr;
listener = instance->Get<Ip6::PlatTcp>().IterateListeners(listener);
VerifyOrQuit(listener == &listener2);
listener = instance->Get<Ip6::PlatTcp>().IterateListeners(listener);
VerifyOrQuit(listener == nullptr);
Log(" otTaskletsProcess");
otTaskletsProcess(instance);
VerifyOrQuit(instance->Get<Ip6::PlatTcp>().GetListeners().GetHead() == &listener2);
VerifyOrQuit(listener2.GetNext() == nullptr);
Log(" Disable second listener");
VerifyOrQuit(instance->Get<Ip6::PlatTcp>().GetListeners().GetHead() == &listener2);
VerifyOrQuit(listener2.GetNext() == nullptr);
sListenerInfo.mListener = &listener2;
listener2.Disable();
listener = nullptr;
listener = instance->Get<Ip6::PlatTcp>().IterateListeners(listener);
VerifyOrQuit(listener == nullptr);
Log(" otTaskletsProcess");
otTaskletsProcess(instance);
VerifyOrQuit(instance->Get<Ip6::PlatTcp>().GetListeners().IsEmpty());
listener = nullptr;
listener = instance->Get<Ip6::PlatTcp>().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<Ip6::PlatTcp>().GetConnections().GetHead() == &connection2);
VerifyOrQuit(connection2.GetNext() == &connection1);
VerifyOrQuit(connection1.GetNext() == nullptr);
connection = nullptr;
connection = instance->Get<Ip6::PlatTcp>().IterateConnections(connection);
VerifyOrQuit(connection == &connection2);
connection = instance->Get<Ip6::PlatTcp>().IterateConnections(connection);
VerifyOrQuit(connection == &connection1);
connection = instance->Get<Ip6::PlatTcp>().IterateConnections(connection);
VerifyOrQuit(connection == nullptr);
Log(" Abort second connection");
sConnInfo.mConnection = &connection2;
connection2.Abort();
VerifyOrQuit(instance->Get<Ip6::PlatTcp>().GetConnections().GetHead() == &connection2);
VerifyOrQuit(connection2.GetNext() == &connection1);
VerifyOrQuit(connection1.GetNext() == nullptr);
connection = nullptr;
connection = instance->Get<Ip6::PlatTcp>().IterateConnections(connection);
VerifyOrQuit(connection == &connection1);
connection = instance->Get<Ip6::PlatTcp>().IterateConnections(connection);
VerifyOrQuit(connection == nullptr);
Log(" otTaskletsProcess");
otTaskletsProcess(instance);
VerifyOrQuit(instance->Get<Ip6::PlatTcp>().GetConnections().GetHead() == &connection1);
VerifyOrQuit(connection1.GetNext() == nullptr);
connection = nullptr;
connection = instance->Get<Ip6::PlatTcp>().IterateConnections(connection);
VerifyOrQuit(connection == &connection1);
connection = instance->Get<Ip6::PlatTcp>().IterateConnections(connection);
VerifyOrQuit(connection == nullptr);
Log(" Abort first connection");
sConnInfo.mConnection = &connection1;
connection1.Abort();
VerifyOrQuit(instance->Get<Ip6::PlatTcp>().GetConnections().GetHead() == &connection1);
VerifyOrQuit(connection1.GetNext() == nullptr);
connection = nullptr;
connection = instance->Get<Ip6::PlatTcp>().IterateConnections(connection);
VerifyOrQuit(connection == nullptr);
Log(" otTaskletsProcess");
otTaskletsProcess(instance);
VerifyOrQuit(instance->Get<Ip6::PlatTcp>().GetConnections().IsEmpty());
connection = nullptr;
connection = instance->Get<Ip6::PlatTcp>().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;
}
+40
View File
@@ -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)
+1
View File
@@ -43,6 +43,7 @@
#include <openthread/platform/misc.h>
#include <openthread/platform/multipan.h>
#include <openthread/platform/radio.h>
#include <openthread/platform/tcp.h>
#include <openthread/platform/trel.h>
#include "common/code_utils.hpp"