// SPDX-License-Identifier: LGPL-3.0-or-later // Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov #ifndef ASYNCTCP_H_ #define ASYNCTCP_H_ #include "AsyncTCPVersion.h" #define ASYNCTCP_FORK_ESP32Async #include "IPAddress.h" #if ESP_IDF_VERSION_MAJOR < 5 #include "IPv6Address.h" #endif #include "lwip/ip6_addr.h" #include "lwip/ip_addr.h" #include #ifndef LIBRETINY #include "sdkconfig.h" extern "C" { #include "freertos/semphr.h" #include "lwip/pbuf.h" } #else extern "C" { #include #include } #define CONFIG_ASYNC_TCP_RUNNING_CORE -1 // any available core #endif // If core is not defined, then we are running in Arduino or PIO #ifndef CONFIG_ASYNC_TCP_RUNNING_CORE #define CONFIG_ASYNC_TCP_RUNNING_CORE -1 // any available core #endif // guard AsyncTCP task with watchdog #ifndef CONFIG_ASYNC_TCP_USE_WDT #define CONFIG_ASYNC_TCP_USE_WDT 1 #endif #ifndef CONFIG_ASYNC_TCP_STACK_SIZE #define CONFIG_ASYNC_TCP_STACK_SIZE 8192 * 2 #endif #ifndef CONFIG_ASYNC_TCP_PRIORITY #define CONFIG_ASYNC_TCP_PRIORITY 10 #endif #ifndef CONFIG_ASYNC_TCP_QUEUE_SIZE #define CONFIG_ASYNC_TCP_QUEUE_SIZE 64 #endif #ifndef CONFIG_ASYNC_TCP_MAX_ACK_TIME #define CONFIG_ASYNC_TCP_MAX_ACK_TIME 5000 #endif class AsyncClient; #define ASYNC_WRITE_FLAG_COPY 0x01 // will allocate new buffer to hold the data while sending (else will hold reference to the data given) #define ASYNC_WRITE_FLAG_MORE 0x02 // will not send PSH flag, meaning that there should be more data to be sent before the application should react. typedef std::function AcConnectHandler; typedef std::function AcAckHandler; typedef std::function AcErrorHandler; typedef std::function AcDataHandler; typedef std::function AcPacketHandler; typedef std::function AcTimeoutHandler; struct tcp_pcb; struct ip_addr; class AsyncClient { public: AsyncClient(tcp_pcb *pcb = 0); ~AsyncClient(); AsyncClient &operator=(const AsyncClient &other); AsyncClient &operator+=(const AsyncClient &other); bool operator==(const AsyncClient &other) const; bool operator!=(const AsyncClient &other) const { return !(*this == other); } bool connect(const IPAddress &ip, uint16_t port); #if ESP_IDF_VERSION_MAJOR < 5 bool connect(const IPv6Address &ip, uint16_t port); #endif bool connect(const char *host, uint16_t port); /** * @brief close connection * * @param now - ignored */ void close(bool now = false); // same as close() void stop() { close(false); }; int8_t abort(); bool free(); // ack is not pending bool canSend() const; // TCP buffer space available size_t space() const; /** * @brief add data to be send (but do not send yet) * @note add() would call lwip's tcp_write() By default apiflags=ASYNC_WRITE_FLAG_COPY You could try to use apiflags with this flag unset to pass data by reference and avoid copy to socket buffer, but looks like it does not work for Arduino's lwip in ESP32/IDF at least it is enforced in https://github.com/espressif/esp-lwip/blob/0606eed9d8b98a797514fdf6eabb4daf1c8c8cd9/src/core/tcp_out.c#L422C5-L422C30 if LWIP_NETIF_TX_SINGLE_PBUF is set, and it is set indeed in IDF https://github.com/espressif/esp-idf/blob/a0f798cfc4bbd624aab52b2c194d219e242d80c1/components/lwip/port/include/lwipopts.h#L744 * * @param data * @param size * @param apiflags * @return size_t amount of data that has been copied */ size_t add(const char *data, size_t size, uint8_t apiflags = ASYNC_WRITE_FLAG_COPY); /** * @brief send data previously add()'ed * * @return true on success * @return false on error */ bool send(); /** * @brief add and enqueue data for sending * @note it is same as add() + send() * @note only make sense when canSend() == true * * @param data * @param size * @param apiflags * @return size_t */ size_t write(const char *data, size_t size, uint8_t apiflags = ASYNC_WRITE_FLAG_COPY); /** * @brief add and enqueue data for sending * @note treats data as null-terminated string * * @param data * @return size_t */ size_t write(const char *data) { return data == NULL ? 0 : write(data, strlen(data)); }; uint8_t state() const; bool connecting() const; bool connected() const; bool disconnecting() const; bool disconnected() const; // disconnected or disconnecting bool freeable() const; uint16_t getMss() const; uint32_t getRxTimeout() const; // no RX data timeout for the connection in seconds void setRxTimeout(uint32_t timeout); uint32_t getAckTimeout() const; // no ACK timeout for the last sent packet in milliseconds void setAckTimeout(uint32_t timeout); void setNoDelay(bool nodelay) const; bool getNoDelay(); void setKeepAlive(uint32_t ms, uint8_t cnt); uint32_t getRemoteAddress() const; uint16_t getRemotePort() const; uint32_t getLocalAddress() const; uint16_t getLocalPort() const; #if LWIP_IPV6 ip6_addr_t getRemoteAddress6() const; ip6_addr_t getLocalAddress6() const; #if ESP_IDF_VERSION_MAJOR < 5 IPv6Address remoteIP6() const; IPv6Address localIP6() const; #else IPAddress remoteIP6() const; IPAddress localIP6() const; #endif #endif // compatibility IPAddress remoteIP() const; uint16_t remotePort() const; IPAddress localIP() const; uint16_t localPort() const; // set callback - on successful connect void onConnect(AcConnectHandler cb, void *arg = 0); // set callback - disconnected void onDisconnect(AcConnectHandler cb, void *arg = 0); // set callback - ack received void onAck(AcAckHandler cb, void *arg = 0); // set callback - unsuccessful connect or error void onError(AcErrorHandler cb, void *arg = 0); // set callback - data received (called if onPacket is not used) void onData(AcDataHandler cb, void *arg = 0); // set callback - data received // !!! You MUST call ackPacket() or free the pbuf yourself to prevent memory leaks void onPacket(AcPacketHandler cb, void *arg = 0); // set callback - ack timeout void onTimeout(AcTimeoutHandler cb, void *arg = 0); // set callback - every 125ms when connected void onPoll(AcConnectHandler cb, void *arg = 0); // ack pbuf from onPacket void ackPacket(struct pbuf *pb); // ack data that you have not acked using the method below size_t ack(size_t len); // will not ack the current packet. Call from onData void ackLater() { _ack_pcb = false; } static const char *errorToString(int8_t error); const char *stateToString() const; // internal callbacks - Do NOT call any of the functions below in user code! static int8_t _s_poll(void *arg, struct tcp_pcb *tpcb); static int8_t _s_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *pb, int8_t err); static int8_t _s_fin(void *arg, struct tcp_pcb *tpcb, int8_t err); static int8_t _s_lwip_fin(void *arg, struct tcp_pcb *tpcb, int8_t err); static void _s_error(void *arg, int8_t err); static int8_t _s_sent(void *arg, struct tcp_pcb *tpcb, uint16_t len); static int8_t _s_connected(void *arg, struct tcp_pcb *tpcb, int8_t err); static void _s_dns_found(const char *name, struct ip_addr *ipaddr, void *arg); static void _tcp_error(void *arg, int8_t err); int8_t _recv(tcp_pcb *pcb, pbuf *pb, int8_t err); tcp_pcb *pcb() { return _pcb; } protected: friend class AsyncServer; bool _connect(ip_addr_t addr, uint16_t port); tcp_pcb *_pcb; int8_t _closed_slot; AcConnectHandler _connect_cb; void *_connect_cb_arg; AcConnectHandler _discard_cb; void *_discard_cb_arg; AcAckHandler _sent_cb; void *_sent_cb_arg; AcErrorHandler _error_cb; void *_error_cb_arg; AcDataHandler _recv_cb; void *_recv_cb_arg; AcPacketHandler _pb_cb; void *_pb_cb_arg; AcTimeoutHandler _timeout_cb; void *_timeout_cb_arg; AcConnectHandler _poll_cb; void *_poll_cb_arg; bool _ack_pcb; uint32_t _tx_last_packet; uint32_t _rx_ack_len; uint32_t _rx_last_packet; uint32_t _rx_timeout; uint32_t _rx_last_ack; uint32_t _ack_timeout; uint16_t _connect_port; int8_t _close(); void _free_closed_slot(); bool _allocate_closed_slot(); int8_t _connected(tcp_pcb *pcb, int8_t err); void _error(int8_t err); int8_t _poll(tcp_pcb *pcb); int8_t _sent(tcp_pcb *pcb, uint16_t len); int8_t _fin(tcp_pcb *pcb, int8_t err); int8_t _lwip_fin(tcp_pcb *pcb, int8_t err); void _dns_found(struct ip_addr *ipaddr); public: AsyncClient *prev; AsyncClient *next; }; class AsyncServer { public: AsyncServer(IPAddress addr, uint16_t port); #if ESP_IDF_VERSION_MAJOR < 5 AsyncServer(IPv6Address addr, uint16_t port); #endif AsyncServer(uint16_t port); ~AsyncServer(); void onClient(AcConnectHandler cb, void *arg); void begin(); void end(); void setNoDelay(bool nodelay); bool getNoDelay() const; uint8_t status() const; // Do not use any of the functions below! static int8_t _s_accept(void *arg, tcp_pcb *newpcb, int8_t err); static int8_t _s_accepted(void *arg, AsyncClient *client); protected: uint16_t _port; bool _bind4 = false; bool _bind6 = false; IPAddress _addr; #if ESP_IDF_VERSION_MAJOR < 5 IPv6Address _addr6; #endif bool _noDelay; tcp_pcb *_pcb; AcConnectHandler _connect_cb; void *_connect_cb_arg; int8_t _accept(tcp_pcb *newpcb, int8_t err); int8_t _accepted(AsyncClient *client); }; #endif /* ASYNCTCP_H_ */