* `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
566 lines
15 KiB
C++
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 */
|