mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 07:49:52 +03:00
add ipv6 support for arduino v3
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
# AsyncTCP
|
# AsyncTCP
|
||||||
|
|
||||||

|

|
||||||
A fork of the [AsyncTCP](https://github.com/me-no-dev/AsyncTCP) library by [@me-no-dev](https://github.com/me-no-dev) for [ESPHome](https://esphome.io).
|
A fork of the [AsyncTCP](https://github.com/me-no-dev/AsyncTCP) library by [@me-no-dev](https://github.com/me-no-dev) for [ESPHome](https://esphome.io).
|
||||||
|
|
||||||
@@ -9,4 +10,5 @@ This is a fully asynchronous TCP library, aimed at enabling trouble-free, multi-
|
|||||||
This library is the base for [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer)
|
This library is the base for [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer)
|
||||||
|
|
||||||
## AsyncClient and AsyncServer
|
## AsyncClient and AsyncServer
|
||||||
|
|
||||||
The base classes on which everything else is built. They expose all possible scenarios, but are really raw and require more skills to use.
|
The base classes on which everything else is built. They expose all possible scenarios, but are really raw and require more skills to use.
|
||||||
|
|||||||
@@ -718,7 +718,7 @@ bool AsyncClient::_connect(ip_addr_t addr, uint16_t port) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
tcp_pcb * pcb = tcp_new_ip_type(addr.type);
|
tcp_pcb * pcb = tcp_new_ip_type(IPADDR_TYPE_ANY);
|
||||||
if (!pcb) {
|
if (!pcb) {
|
||||||
log_e("pcb == NULL");
|
log_e("pcb == NULL");
|
||||||
return false;
|
return false;
|
||||||
@@ -735,12 +735,19 @@ bool AsyncClient::_connect(ip_addr_t addr, uint16_t port) {
|
|||||||
|
|
||||||
bool AsyncClient::connect(IPAddress ip, uint16_t port) {
|
bool AsyncClient::connect(IPAddress ip, uint16_t port) {
|
||||||
ip_addr_t addr;
|
ip_addr_t addr;
|
||||||
addr.type = IPADDR_TYPE_V4;
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
|
// ip_addr_set_ip4_u32(&addr, ip);
|
||||||
addr.u_addr.ip4.addr = ip;
|
addr.u_addr.ip4.addr = ip;
|
||||||
|
addr.type = IPADDR_TYPE_V4;
|
||||||
|
ip_clear_no4(&addr);
|
||||||
|
#else
|
||||||
|
ip.to_ip_addr_t(&addr);
|
||||||
|
#endif
|
||||||
|
|
||||||
return _connect(addr, port);
|
return _connect(addr, port);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if LWIP_IPV6 && ESP_IDF_VERSION_MAJOR < 5
|
||||||
bool AsyncClient::connect(IPv6Address ip, uint16_t port) {
|
bool AsyncClient::connect(IPv6Address ip, uint16_t port) {
|
||||||
ip_addr_t addr;
|
ip_addr_t addr;
|
||||||
addr.type = IPADDR_TYPE_V6;
|
addr.type = IPADDR_TYPE_V6;
|
||||||
@@ -748,6 +755,7 @@ bool AsyncClient::connect(IPv6Address ip, uint16_t port) {
|
|||||||
|
|
||||||
return _connect(addr, port);
|
return _connect(addr, port);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
bool AsyncClient::connect(const char * host, uint16_t port) {
|
bool AsyncClient::connect(const char * host, uint16_t port) {
|
||||||
ip_addr_t addr;
|
ip_addr_t addr;
|
||||||
@@ -759,13 +767,17 @@ bool AsyncClient::connect(const char * host, uint16_t port) {
|
|||||||
|
|
||||||
err_t err = dns_gethostbyname(host, &addr, (dns_found_callback)&_tcp_dns_found, this);
|
err_t err = dns_gethostbyname(host, &addr, (dns_found_callback)&_tcp_dns_found, this);
|
||||||
if (err == ERR_OK) {
|
if (err == ERR_OK) {
|
||||||
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
|
#if LWIP_IPV6
|
||||||
if (addr.type == IPADDR_TYPE_V6) {
|
if (addr.type == IPADDR_TYPE_V6) {
|
||||||
return connect(IPv6Address(addr.u_addr.ip6.addr), port);
|
return connect(IPv6Address(addr.u_addr.ip6.addr), port);
|
||||||
}
|
}
|
||||||
#if LWIP_IPV6
|
|
||||||
return connect(IPAddress(addr.u_addr.ip4.addr), port);
|
return connect(IPAddress(addr.u_addr.ip4.addr), port);
|
||||||
#else
|
#else
|
||||||
return connect(IPAddress(addr.addr), port);
|
return connect(IPAddress(addr.addr), port);
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
return _connect(addr, port);
|
||||||
#endif
|
#endif
|
||||||
} else if (err == ERR_INPROGRESS) {
|
} else if (err == ERR_INPROGRESS) {
|
||||||
_connect_port = port;
|
_connect_port = port;
|
||||||
@@ -1031,10 +1043,17 @@ int8_t AsyncClient::_poll(tcp_pcb * pcb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void AsyncClient::_dns_found(struct ip_addr * ipaddr) {
|
void AsyncClient::_dns_found(struct ip_addr * ipaddr) {
|
||||||
if (ipaddr && ipaddr->u_addr.ip4.addr) {
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
connect(IPAddress(ipaddr->u_addr.ip4.addr), _connect_port);
|
if (ipaddr && IP_IS_V4(ipaddr)) {
|
||||||
|
connect(IPAddress(ip_addr_get_ip4_u32(ipaddr)), _connect_port);
|
||||||
|
#if LWIP_IPV6
|
||||||
} else if (ipaddr && ipaddr->u_addr.ip6.addr) {
|
} else if (ipaddr && ipaddr->u_addr.ip6.addr) {
|
||||||
connect(IPv6Address(ipaddr->u_addr.ip6.addr), _connect_port);
|
connect(IPv6Address(ipaddr->u_addr.ip6.addr), _connect_port);
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
if (ipaddr) {
|
||||||
|
connect(IPAddress(ipaddr), _connect_port);
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
if (_error_cb) {
|
if (_error_cb) {
|
||||||
_error_cb(_error_cb_arg, this, -55);
|
_error_cb(_error_cb_arg, this, -55);
|
||||||
@@ -1130,6 +1149,7 @@ uint32_t AsyncClient::getRemoteAddress() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if LWIP_IPV6
|
||||||
ip6_addr_t AsyncClient::getRemoteAddress6() {
|
ip6_addr_t AsyncClient::getRemoteAddress6() {
|
||||||
if (!_pcb) {
|
if (!_pcb) {
|
||||||
ip6_addr_t nulladdr;
|
ip6_addr_t nulladdr;
|
||||||
@@ -1139,6 +1159,33 @@ ip6_addr_t AsyncClient::getRemoteAddress6() {
|
|||||||
return _pcb->remote_ip.u_addr.ip6;
|
return _pcb->remote_ip.u_addr.ip6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ip6_addr_t AsyncClient::getLocalAddress6() {
|
||||||
|
if (!_pcb) {
|
||||||
|
ip6_addr_t nulladdr;
|
||||||
|
ip6_addr_set_zero(&nulladdr);
|
||||||
|
return nulladdr;
|
||||||
|
}
|
||||||
|
return _pcb->local_ip.u_addr.ip6;
|
||||||
|
}
|
||||||
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
|
IPv6Address AsyncClient::remoteIP6() {
|
||||||
|
return IPv6Address(getRemoteAddress6().addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
IPv6Address AsyncClient::localIP6() {
|
||||||
|
return IPv6Address(getLocalAddress6().addr);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
IPAddress AsyncClient::remoteIP6() {
|
||||||
|
return _pcb ? IPAddress(dynamic_cast<const ip_addr_t *>(&_pcb->remote_ip)) : IPAddress(IPType::IPv6);
|
||||||
|
}
|
||||||
|
|
||||||
|
IPAddress AsyncClient::localIP6() {
|
||||||
|
return _pcb ? IPAddress(dynamic_cast<const ip_addr_t *>(&_pcb->local_ip)) : IPAddress(IPType::IPv6);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
uint16_t AsyncClient::getRemotePort() {
|
uint16_t AsyncClient::getRemotePort() {
|
||||||
if (!_pcb) {
|
if (!_pcb) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -1157,15 +1204,6 @@ uint32_t AsyncClient::getLocalAddress() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
ip6_addr_t AsyncClient::getLocalAddress6() {
|
|
||||||
if (!_pcb) {
|
|
||||||
ip6_addr_t nulladdr;
|
|
||||||
ip6_addr_set_zero(&nulladdr);
|
|
||||||
return nulladdr;
|
|
||||||
}
|
|
||||||
return _pcb->local_ip.u_addr.ip6;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t AsyncClient::getLocalPort() {
|
uint16_t AsyncClient::getLocalPort() {
|
||||||
if (!_pcb) {
|
if (!_pcb) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -1174,11 +1212,11 @@ uint16_t AsyncClient::getLocalPort() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
IPAddress AsyncClient::remoteIP() {
|
IPAddress AsyncClient::remoteIP() {
|
||||||
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
return IPAddress(getRemoteAddress());
|
return IPAddress(getRemoteAddress());
|
||||||
}
|
#else
|
||||||
|
return _pcb ? IPAddress(dynamic_cast<const ip_addr_t *>(&_pcb->remote_ip)) : IPAddress();
|
||||||
IPv6Address AsyncClient::remoteIP6() {
|
#endif
|
||||||
return IPv6Address(getRemoteAddress6().addr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t AsyncClient::remotePort() {
|
uint16_t AsyncClient::remotePort() {
|
||||||
@@ -1186,12 +1224,13 @@ uint16_t AsyncClient::remotePort() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
IPAddress AsyncClient::localIP() {
|
IPAddress AsyncClient::localIP() {
|
||||||
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
return IPAddress(getLocalAddress());
|
return IPAddress(getLocalAddress());
|
||||||
|
#else
|
||||||
|
return _pcb ? IPAddress(dynamic_cast<const ip_addr_t *>(&_pcb->local_ip)) : IPAddress();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
IPv6Address AsyncClient::localIP6() {
|
|
||||||
return IPv6Address(getLocalAddress6().addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t AsyncClient::localPort() {
|
uint16_t AsyncClient::localPort() {
|
||||||
return getLocalPort();
|
return getLocalPort();
|
||||||
@@ -1355,7 +1394,12 @@ int8_t AsyncClient::_s_connected(void * arg, void * pcb, int8_t err) {
|
|||||||
|
|
||||||
AsyncServer::AsyncServer(IPAddress addr, uint16_t port)
|
AsyncServer::AsyncServer(IPAddress addr, uint16_t port)
|
||||||
: _port(port)
|
: _port(port)
|
||||||
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
, _bind4(true)
|
, _bind4(true)
|
||||||
|
#else
|
||||||
|
, _bind4(addr.type() != IPType::IPv6)
|
||||||
|
, _bind6(addr.type() == IPType::IPv6)
|
||||||
|
#endif
|
||||||
, _addr(addr)
|
, _addr(addr)
|
||||||
, _noDelay(false)
|
, _noDelay(false)
|
||||||
, _pcb(0)
|
, _pcb(0)
|
||||||
@@ -1363,6 +1407,7 @@ AsyncServer::AsyncServer(IPAddress addr, uint16_t port)
|
|||||||
, _connect_cb_arg(0) {
|
, _connect_cb_arg(0) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
AsyncServer::AsyncServer(IPv6Address addr, uint16_t port)
|
AsyncServer::AsyncServer(IPv6Address addr, uint16_t port)
|
||||||
: _port(port)
|
: _port(port)
|
||||||
, _bind6(true)
|
, _bind6(true)
|
||||||
@@ -1372,13 +1417,16 @@ AsyncServer::AsyncServer(IPv6Address addr, uint16_t port)
|
|||||||
, _connect_cb(0)
|
, _connect_cb(0)
|
||||||
, _connect_cb_arg(0) {
|
, _connect_cb_arg(0) {
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
AsyncServer::AsyncServer(uint16_t port)
|
AsyncServer::AsyncServer(uint16_t port)
|
||||||
: _port(port)
|
: _port(port)
|
||||||
, _bind4(true)
|
, _bind4(true)
|
||||||
, _bind6(true)
|
, _bind6(true)
|
||||||
, _addr((uint32_t)IPADDR_ANY)
|
, _addr((uint32_t)IPADDR_ANY)
|
||||||
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
, _addr6()
|
, _addr6()
|
||||||
|
#endif
|
||||||
, _noDelay(false)
|
, _noDelay(false)
|
||||||
, _pcb(0)
|
, _pcb(0)
|
||||||
, _connect_cb(0)
|
, _connect_cb(0)
|
||||||
@@ -1420,9 +1468,17 @@ void AsyncServer::begin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ip_addr_t local_addr;
|
ip_addr_t local_addr;
|
||||||
local_addr.type = bind_type;
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
local_addr.u_addr.ip4.addr = (uint32_t)_addr;
|
// ip_addr_set_ip4_u32(&local_addr, _addr);
|
||||||
memcpy(local_addr.u_addr.ip6.addr, static_cast<const uint32_t *>(_addr6), sizeof(uint32_t) * 4);
|
local_addr.u_addr.ip4.addr = _addr;
|
||||||
|
local_addr.type = IPADDR_TYPE_V4;
|
||||||
|
ip_clear_no4(&local_addr);
|
||||||
|
/* local_addr.type = bind_type;
|
||||||
|
local_addr.u_addr.ip4.addr = (uint32_t) _addr;
|
||||||
|
memcpy(local_addr.u_addr.ip6.addr, static_cast<const uint32_t*>(_addr6), sizeof(uint32_t) * 4); */
|
||||||
|
#else
|
||||||
|
_addr.to_ip_addr_t(&local_addr);
|
||||||
|
#endif
|
||||||
err = _tcp_bind(_pcb, &local_addr, _port);
|
err = _tcp_bind(_pcb, &local_addr, _port);
|
||||||
|
|
||||||
if (err != ERR_OK) {
|
if (err != ERR_OK) {
|
||||||
|
|||||||
@@ -23,16 +23,18 @@
|
|||||||
#define ASYNCTCP_H_
|
#define ASYNCTCP_H_
|
||||||
|
|
||||||
#include "IPAddress.h"
|
#include "IPAddress.h"
|
||||||
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
#include "IPv6Address.h"
|
#include "IPv6Address.h"
|
||||||
|
#endif
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include "lwip/ip_addr.h"
|
||||||
|
#include "lwip/ip6_addr.h"
|
||||||
|
|
||||||
#ifndef LIBRETINY
|
#ifndef LIBRETINY
|
||||||
#include "sdkconfig.h"
|
#include "sdkconfig.h"
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include "freertos/semphr.h"
|
#include "freertos/semphr.h"
|
||||||
#include "lwip/pbuf.h"
|
#include "lwip/pbuf.h"
|
||||||
#include "lwip/ip_addr.h"
|
|
||||||
#include "lwip/ip6_addr.h"
|
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@@ -46,20 +48,21 @@ extern "C" {
|
|||||||
//If core is not defined, then we are running in Arduino or PIO
|
//If core is not defined, then we are running in Arduino or PIO
|
||||||
#ifndef CONFIG_ASYNC_TCP_RUNNING_CORE
|
#ifndef CONFIG_ASYNC_TCP_RUNNING_CORE
|
||||||
#define CONFIG_ASYNC_TCP_RUNNING_CORE -1 //any available core
|
#define CONFIG_ASYNC_TCP_RUNNING_CORE -1 //any available core
|
||||||
#define CONFIG_ASYNC_TCP_USE_WDT 0 //if enabled, adds between 33us and 200us per event
|
// Note default was 1 and previously set to 0 for EMS-ESP32
|
||||||
|
#define CONFIG_ASYNC_TCP_USE_WDT 1 //if enabled, adds between 33us and 200us per event
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef CONFIG_ASYNC_TCP_TASK_PRIORITY
|
#ifndef CONFIG_ASYNC_TCP_TASK_PRIORITY
|
||||||
#define CONFIG_ASYNC_TCP_TASK_PRIORITY 5
|
#define CONFIG_ASYNC_TCP_TASK_PRIORITY 5
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// stack usage measured: ESP32: ~2.3K, ESP32S3: ~3.5k
|
// EMS-ESP32: stack usage measured: ESP32: ~2.3K, ESP32S3: ~3.5k
|
||||||
#ifndef CONFIG_ASYNC_TCP_STACK_SIZE
|
#ifndef CONFIG_ASYNC_TCP_STACK_SIZE
|
||||||
#define CONFIG_ASYNC_TCP_STACK_SIZE 5120
|
#define CONFIG_ASYNC_TCP_STACK_SIZE 5120
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
// maybe enlarge queue to 64 or 128 see https://github.com/emsesp/EMS-ESP32/issues/177
|
// EMS-ESP32: maybe enlarge queue to 64 or 128 see https://github.com/emsesp/EMS-ESP32/issues/177
|
||||||
#ifndef CONFIG_ASYNC_TCP_QUEUE
|
#ifndef CONFIG_ASYNC_TCP_QUEUE
|
||||||
#define CONFIG_ASYNC_TCP_QUEUE 32
|
#define CONFIG_ASYNC_TCP_QUEUE 32
|
||||||
#endif
|
#endif
|
||||||
@@ -94,7 +97,9 @@ class AsyncClient {
|
|||||||
return !(*this == other);
|
return !(*this == other);
|
||||||
}
|
}
|
||||||
bool connect(IPAddress ip, uint16_t port);
|
bool connect(IPAddress ip, uint16_t port);
|
||||||
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
bool connect(IPv6Address ip, uint16_t port);
|
bool connect(IPv6Address ip, uint16_t port);
|
||||||
|
#endif
|
||||||
bool connect(const char * host, uint16_t port);
|
bool connect(const char * host, uint16_t port);
|
||||||
void close(bool now = false);
|
void close(bool now = false);
|
||||||
void stop();
|
void stop();
|
||||||
@@ -129,18 +134,25 @@ class AsyncClient {
|
|||||||
bool getNoDelay();
|
bool getNoDelay();
|
||||||
|
|
||||||
uint32_t getRemoteAddress();
|
uint32_t getRemoteAddress();
|
||||||
ip6_addr_t getRemoteAddress6();
|
|
||||||
uint16_t getRemotePort();
|
uint16_t getRemotePort();
|
||||||
uint32_t getLocalAddress();
|
uint32_t getLocalAddress();
|
||||||
ip6_addr_t getLocalAddress6();
|
|
||||||
uint16_t getLocalPort();
|
uint16_t getLocalPort();
|
||||||
|
#if LWIP_IPV6
|
||||||
|
ip6_addr_t getRemoteAddress6();
|
||||||
|
ip6_addr_t getLocalAddress6();
|
||||||
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
|
IPv6Address remoteIP6();
|
||||||
|
IPv6Address localIP6();
|
||||||
|
#else
|
||||||
|
IPAddress remoteIP6();
|
||||||
|
IPAddress localIP6();
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
//compatibility
|
//compatibility
|
||||||
IPAddress remoteIP();
|
IPAddress remoteIP();
|
||||||
IPv6Address remoteIP6();
|
|
||||||
uint16_t remotePort();
|
uint16_t remotePort();
|
||||||
IPAddress localIP();
|
IPAddress localIP();
|
||||||
IPv6Address localIP6();
|
|
||||||
uint16_t localPort();
|
uint16_t localPort();
|
||||||
|
|
||||||
void onConnect(AcConnectHandler cb, void * arg = 0); //on successful connect
|
void onConnect(AcConnectHandler cb, void * arg = 0); //on successful connect
|
||||||
@@ -227,7 +239,9 @@ class AsyncClient {
|
|||||||
class AsyncServer {
|
class AsyncServer {
|
||||||
public:
|
public:
|
||||||
AsyncServer(IPAddress addr, uint16_t port);
|
AsyncServer(IPAddress addr, uint16_t port);
|
||||||
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
AsyncServer(IPv6Address addr, uint16_t port);
|
AsyncServer(IPv6Address addr, uint16_t port);
|
||||||
|
#endif
|
||||||
AsyncServer(uint16_t port);
|
AsyncServer(uint16_t port);
|
||||||
~AsyncServer();
|
~AsyncServer();
|
||||||
void onClient(AcConnectHandler cb, void * arg);
|
void onClient(AcConnectHandler cb, void * arg);
|
||||||
@@ -246,7 +260,9 @@ class AsyncServer {
|
|||||||
bool _bind4 = false;
|
bool _bind4 = false;
|
||||||
bool _bind6 = false;
|
bool _bind6 = false;
|
||||||
IPAddress _addr;
|
IPAddress _addr;
|
||||||
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
IPv6Address _addr6;
|
IPv6Address _addr6;
|
||||||
|
#endif
|
||||||
bool _noDelay;
|
bool _noDelay;
|
||||||
tcp_pcb * _pcb;
|
tcp_pcb * _pcb;
|
||||||
AcConnectHandler _connect_cb;
|
AcConnectHandler _connect_cb;
|
||||||
|
|||||||
Reference in New Issue
Block a user