Tasmota/lib/libesp32/ESP-Mail-Client/src/extras/WiFiClientImpl.h
s-hadinger c2f8821b2f
Sendmail upgraded to ESP-Mail-Client v3.4.9 from v1.2.0, using BearSSL instead of MbedTLS (#19460)
* `Sendmail` upgraded to ESP-Mail-Client v3.4.9 from v1.2.0, using BearSSL instead of MbedTLS

* Fix compilation on ESP8266

* Fix compilation

* fix compilation
2023-09-04 23:00:37 +02:00

566 lines
15 KiB
C++

/**
* WiFiClientImpl v1.0.1
*
* This library provides the base client in replacement of ESP32 WiFiClient.
*
* The WiFiClient in ESP32 cannot be used in multithreading environment as in FreeRTOS task
* which can (always) lead to the assetion error "pbuf_free: p->ref > 0".
*
* Created August 20, 2023
*
* The MIT License (MIT)
* Copyright (c) 2022 K. Suwatchai (Mobizt)
*
*
* Permission is hereby granted, free of charge, to any person returning a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#if !defined(WIFICLIENT_IMPL_H) && defined(ESP32)
#define WIFICLIENT_IMPL_H
#include <lwip/sockets.h>
class WiFiClientImpl : public Client
{
public:
WiFiClientImpl(){};
virtual ~WiFiClientImpl() { tcpClose(); };
int connect(IPAddress ip, uint16_t port) { return tcpConnect(ip, port, _timeout); }
int connect(IPAddress ip, uint16_t port, int32_t timeout_ms) { return tcpConnect(ip, port, timeout_ms); }
int connect(const char *host, uint16_t port)
{
IPAddress address((uint32_t)0);
#if defined(WIFI_HAS_HOST_BY_NAME)
if (!WiFiGenericClass::hostByName(host, address))
return -1;
#endif
return tcpConnect(address, port, _timeout);
}
int connect(const char *host, uint16_t port, int32_t timeout_ms)
{
_timeout = timeout_ms;
return connect(host, port);
}
size_t write(uint8_t data) { return write(&data, 1); }
size_t write(const uint8_t *buf, size_t size) { return tcpWrite(buf, size); }
size_t write_P(PGM_P buf, size_t size) { return write(buf, size); }
size_t write(Stream &stream)
{
uint8_t *buf = (uint8_t *)malloc(1360);
if (!buf)
{
return 0;
}
size_t toRead = 0, toWrite = 0, written = 0;
size_t available = stream.available();
while (available)
{
toRead = (available > 1360) ? 1360 : available;
toWrite = stream.readBytes(buf, toRead);
written += write(buf, toWrite);
available = stream.available();
}
free(buf);
buf = nullptr;
return written;
}
int available() { return tcpAavailable(); }
int read()
{
uint8_t data = 0;
int res = read(&data, 1);
if (res < 0)
{
return res;
}
if (res == 0)
{ // No data available.
return -1;
}
return data;
}
int read(uint8_t *buf, size_t size) { return tcpRead(buf, size); }
int peek() { return tcpPeek(); }
void flush()
{
if (r_available())
fillRxBuffer();
_fillPos = _fillSize;
}
void stop() { tcpClose(); }
uint8_t connected() { return tcpConnected(); }
operator bool()
{
return connected();
}
WiFiClientImpl &operator=(const WiFiClientImpl &other);
bool operator==(const bool value)
{
return bool() == value;
}
bool operator!=(const bool value)
{
return bool() != value;
}
bool operator==(const WiFiClientImpl &);
bool operator!=(const WiFiClientImpl &rhs)
{
return !this->operator==(rhs);
};
virtual int fd() const { return _socket; }
int setSocketOption(int option, char *value, size_t len)
{
return setSocketOption(SOL_SOCKET, option, (const void *)value, len);
}
int setSocketOption(int level, int option, const void *value, size_t len)
{
int res = setsockopt(_socket, level, option, value, len);
if (res < 0)
{
log_e("fail on %d, errno: %d, \"%s\"", _socket, errno, strerror(errno));
}
return res;
}
int setOption(int option, int *value)
{
return setSocketOption(IPPROTO_TCP, option, (const void *)value, sizeof(int));
}
int getOption(int option, int *value)
{
socklen_t size = sizeof(int);
int res = getsockopt(_socket, IPPROTO_TCP, option, (char *)value, &size);
if (res < 0)
{
log_e("fail on fd %d, errno: %d, \"%s\"", _socket, errno, strerror(errno));
}
return res;
}
int setTimeout(uint32_t seconds)
{
Client::setTimeout(seconds * 1000); // This should be here?
_timeout = seconds * 1000;
if (_socket >= 0)
{
struct timeval tv;
tv.tv_sec = seconds;
tv.tv_usec = 0;
if (setSocketOption(SO_RCVTIMEO, (char *)&tv, sizeof(struct timeval)) < 0)
{
return -1;
}
return setSocketOption(SO_SNDTIMEO, (char *)&tv, sizeof(struct timeval));
}
else
{
return 0;
}
}
int setNoDelay(bool nodelay)
{
int flag = nodelay;
return setOption(TCP_NODELAY, &flag);
}
bool getNoDelay()
{
int flag = 0;
getOption(TCP_NODELAY, &flag);
return flag;
}
IPAddress remoteIP() const
{
return remoteIP(_socket);
}
IPAddress remoteIP(int fd) const
{
struct sockaddr_storage addr;
socklen_t len = sizeof addr;
getpeername(fd, (struct sockaddr *)&addr, &len);
struct sockaddr_in *s = (struct sockaddr_in *)&addr;
return IPAddress((uint32_t)(s->sin_addr.s_addr));
}
uint16_t remotePort() const
{
return remotePort(_socket);
}
uint16_t remotePort(int fd) const
{
struct sockaddr_storage addr;
socklen_t len = sizeof addr;
getpeername(fd, (struct sockaddr *)&addr, &len);
struct sockaddr_in *s = (struct sockaddr_in *)&addr;
return ntohs(s->sin_port);
}
IPAddress localIP() const
{
return localIP(_socket);
}
IPAddress localIP(int fd) const
{
struct sockaddr_storage addr;
socklen_t len = sizeof addr;
getsockname(fd, (struct sockaddr *)&addr, &len);
struct sockaddr_in *s = (struct sockaddr_in *)&addr;
return IPAddress((uint32_t)(s->sin_addr.s_addr));
}
uint16_t localPort() const
{
return localPort(_socket);
}
uint16_t localPort(int fd) const
{
struct sockaddr_storage addr;
socklen_t len = sizeof addr;
getsockname(fd, (struct sockaddr *)&addr, &len);
struct sockaddr_in *s = (struct sockaddr_in *)&addr;
return ntohs(s->sin_port);
}
// friend class WiFiServer;
using Print::write;
private:
int _socket = -1;
int _timeout = 30000;
size_t _rxBuffSize = 2048;
uint8_t *_rxBuff = nullptr;
size_t _fillPos = 0;
size_t _fillSize = 0;
bool _failed = false;
bool _connected = false;
size_t r_available()
{
if (_socket < 0)
{
return 0;
}
int count;
#ifdef ESP_IDF_VERSION_MAJOR
int res = lwip_ioctl(_socket, FIONREAD, &count);
#else
int res = lwip_ioctl_r(_socket, FIONREAD, &count);
#endif
if (res < 0)
{
_failed = true;
return 0;
}
return count;
}
size_t fillRxBuffer()
{
if (!_rxBuff && !allocRxBuffer(_rxBuffSize))
return 0;
if (_fillSize && _fillPos == _fillSize)
{
_fillSize = 0;
_fillPos = 0;
}
if (!_rxBuff || _rxBuffSize <= _fillSize || !r_available())
{
return 0;
}
int res = recv(_socket, _rxBuff + _fillSize, _rxBuffSize - _fillSize, MSG_DONTWAIT);
if (res < 0)
{
if (errno != EWOULDBLOCK)
{
_failed = true;
}
return 0;
}
_fillSize += res;
return res;
}
bool failed()
{
return _failed;
}
int tcpConnect(const IPAddress &ip, uint32_t port, int timeout)
{
int enable = 1;
log_v("Starting socket");
_socket = -1;
_socket = lwip_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (_socket < 0)
{
return _socket;
}
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = ip;
serv_addr.sin_port = htons(port);
if (timeout <= 0)
timeout = 30000; // Milli seconds.
fd_set fdset;
struct timeval tv;
FD_ZERO(&fdset);
FD_SET(_socket, &fdset);
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) * 1000;
int res = lwip_connect(_socket, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
if (res < 0 && errno != EINPROGRESS)
{
log_e("connect on fd %d, errno: %d, \"%s\"", _socket, errno, strerror(errno));
tcpClose();
return -1;
}
res = select(_socket + 1, nullptr, &fdset, nullptr, timeout < 0 ? nullptr : &tv);
if (res < 0)
{
log_e("select on fd %d, errno: %d, \"%s\"", _socket, errno, strerror(errno));
tcpClose();
return -1;
}
else if (res == 0)
{
log_i("select returned due to timeout %d ms for fd %d", timeout, _socket);
tcpClose();
return -1;
}
else
{
int sockerr;
socklen_t len = (socklen_t)sizeof(int);
res = getsockopt(_socket, SOL_SOCKET, SO_ERROR, &sockerr, &len);
if (res < 0)
{
log_e("getsockopt on fd %d, errno: %d, \"%s\"", _socket, errno, strerror(errno));
tcpClose();
return -1;
}
if (sockerr != 0)
{
log_e("socket error on fd %d, errno: %d, \"%s\"", _socket, sockerr, strerror(sockerr));
tcpClose();
return -1;
}
}
lwip_setsockopt(_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
lwip_setsockopt(_socket, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
lwip_setsockopt(_socket, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable));
lwip_setsockopt(_socket, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable));
fcntl(_socket, F_SETFL, fcntl(_socket, F_GETFL, 0) | O_NONBLOCK);
_connected = true;
return 1;
}
void tcpClose()
{
lwip_close(_socket);
_socket = -1;
_connected = false;
freeRxBuffer();
}
int tcpAavailable()
{
return _fillSize - _fillPos + r_available();
}
int tcpPeek()
{
if (!_rxBuff || _fillPos == _fillSize && !fillRxBuffer())
{
return -1;
}
return _rxBuff[_fillPos];
}
size_t tcpWrite(const uint8_t *buf, size_t size)
{
if (!tcpConnected() || !size)
return 0;
int res = 0;
int retry = 10;
int socketFileDescriptor = _socket;
size_t totalBytesSent = 0;
size_t bytesRemaining = size;
while (retry)
{
// use select to make sure the socket is ready for writing
fd_set set;
struct timeval tv;
FD_ZERO(&set); // empties the set
FD_SET(socketFileDescriptor, &set); // adds FD to the set
tv.tv_sec = 0;
tv.tv_usec = 1000000;
retry--;
if (select(socketFileDescriptor + 1, NULL, &set, NULL, &tv) < 0)
{
return 0;
}
if (FD_ISSET(socketFileDescriptor, &set))
{
res = send(socketFileDescriptor, (void *)buf, bytesRemaining, MSG_DONTWAIT);
if (res > 0)
{
totalBytesSent += res;
if (totalBytesSent >= size)
{
// completed successfully
retry = 0;
}
else
{
buf += res;
bytesRemaining -= res;
retry = 10;
}
}
else if (res < 0)
{
log_e("fail on fd %d, errno: %d, \"%s\"", _socket, errno, strerror(errno));
if (errno != EAGAIN)
{
// if resource was busy, can try again, otherwise give up
res = 0;
retry = 0;
}
}
else
{
// Try again
}
}
}
return totalBytesSent;
// return ssize_t or signed size_t for error
return lwip_write(_socket, buf, size);
}
size_t tcpRead(uint8_t *dst, size_t len)
{
if (!dst || !len || (_fillPos == _fillSize && !fillRxBuffer()))
{
return _failed ? -1 : 0;
}
int remain = _fillSize - _fillPos;
if (len <= remain || ((len - remain) <= (_rxBuffSize - _fillSize) && fillRxBuffer() >= (len - remain)))
{
if (len == 1)
{
*dst = _rxBuff[_fillPos];
}
else
{
memcpy(dst, _rxBuff + _fillPos, len);
}
_fillPos += len;
return len;
}
size_t left = len;
size_t toRead = remain;
uint8_t *buf = dst;
memcpy(buf, _rxBuff + _fillPos, toRead);
_fillPos += toRead;
left -= toRead;
buf += toRead;
while (left)
{
if (!fillRxBuffer())
{
return len - left;
}
remain = _fillSize - _fillPos;
toRead = (remain > left) ? left : remain;
memcpy(buf, _rxBuff + _fillPos, toRead);
_fillPos += toRead;
left -= toRead;
buf += toRead;
}
return len;
}
int tcpConnected()
{
return _socket >= 0;
}
bool allocRxBuffer(size_t size)
{
if (_rxBuff)
freeRxBuffer();
_rxBuff = (uint8_t *)malloc(size);
if (!_rxBuff)
{
log_e("Not enough memory to allocate buffer");
_failed = true;
return false;
}
return true;
}
void freeRxBuffer()
{
if (_rxBuff)
free(_rxBuff);
_rxBuff = nullptr;
}
};
#endif /* WIFICLIENT_IMPL_H */