mirror of
https://github.com/espressif/openthread.git
synced 2026-08-11 05:07:47 +00:00
[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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user