Skip to content

File LwIPServer.cpp

File List > arduino > libraries > common > WiFiServer > LwIPServer.cpp

Go to the documentation of this file.

/* Copyright (c) Kuba Szczodrzyński 2022-04-26. */

#if LT_ARD_HAS_WIFI && LT_HAS_LWIP

#include "LwIPServer.h"

// disable #defines removing lwip_ prefix
#undef LWIP_COMPAT_SOCKETS
#define LWIP_COMPAT_SOCKETS 0

extern "C" {
#include <lwip/api.h>
// #include <lwip/dns.h>
#include <lwip/err.h>
#include <lwip/sockets.h>
#include <sys/time.h>
}

LwIPServer::LwIPServer(uint32_t addr, uint16_t port, uint8_t maxClients)
    : _sock(-1), _sockAccepted(-1), _addr(addr), _port(port), _maxClients(maxClients), _active(false), _noDelay(false) {
}

LwIPServer::operator bool() {
    return _active;
}

bool LwIPServer::begin(uint16_t port, bool reuseAddr) {
    if (_active)
        return true;
    if (port)
        _port = port;

    _sock = lwip_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (_sock < 0) {
        LT_EM(SERVER, "Socket failed; errno=%d", errno);
        return false;
    }

    int enable = reuseAddr;
    lwip_setsockopt(_sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));

    struct sockaddr_in addr;
    addr.sin_family      = AF_INET;
    addr.sin_addr.s_addr = _addr;
    addr.sin_port        = htons(_port);

    if (lwip_bind(_sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        LT_EM(SERVER, "Bind failed; errno=%d", errno);
        return false;
    }

    if (lwip_listen(_sock, _maxClients) < 0) {
        LT_EM(SERVER, "Bind failed; errno=%d", errno);
        return false;
    }

    uint8_t *addrB = (uint8_t *)&_addr;
    LT_IM(SERVER, "Server running on %hhu.%hhu.%hhu.%hhu:%hu", addrB[0], addrB[1], addrB[2], addrB[3], _port);

    lwip_fcntl(_sock, F_SETFL, O_NONBLOCK);
    _active       = true;
    _noDelay      = false;
    _sockAccepted = -1;
    return true;
}

void LwIPServer::end() {
    if (_sock == -1)
        return;
    lwip_close(_sock);
    _sock   = -1;
    _active = -1;
}

WiFiClient LwIPServer::accept() {
    if (!_active)
        return WiFiClient();

    int sock;
    if (_sockAccepted >= 0) {
        sock          = _sockAccepted;
        _sockAccepted = -1;
    } else {
        struct sockaddr_in addr;
        socklen_t len = sizeof(addr);
        sock          = lwip_accept(_sock, (struct sockaddr *)&addr, &len);
    }

    if (sock >= 0) {
        int enable = 1;
        if (lwip_setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable)) == ERR_OK) {
            enable = _noDelay;
            if (lwip_setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)) == ERR_OK) {
                // HOTFIX: allow the TCP thread to receive data
                // I'm not sure what's happening there, so this should probably be fixed properly.
                // When a connection arrives, sometimes TCP hasn't received anything yet. This causes
                // calling WiFiClient::connected() check for lwip_recv(), which returns EWOULDBLOCK
                // as the client is still connected. The problem is that there's basically an infinite loop
                // created: nowhere in that code is a yield()/delay() that would allow TCP thread to work
                // and receive data, so LwIP still sees a connected client that sends nothing. At least
                // that's what I understand. And any loop that doesn't call delay() seems to block the TCP
                // stack completely and prevents it from even being pinged.
                LT_DM(SERVER, "Got client");
                delay(5);
                return WiFiClient(sock);
            }
        }
    }

    return WiFiClient();
}

int LwIPServer::setTimeout(uint32_t seconds) {
    struct timeval tv;
    tv.tv_sec  = seconds;
    tv.tv_usec = 0;
    if (lwip_setsockopt(_sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0)
        return -1;
    return lwip_setsockopt(_sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
}

void LwIPServer::setNoDelay(bool noDelay) {
    _noDelay = noDelay;
}

bool LwIPServer::getNoDelay() {
    return _noDelay;
}

bool LwIPServer::hasClient() {
    if (_sockAccepted >= 0) {
        return true;
    }
    struct sockaddr_in addr;
    socklen_t len = sizeof(addr);
    _sockAccepted = lwip_accept(_sock, (struct sockaddr *)&addr, &len);
    if (_sockAccepted >= 0) {
        return true;
    }
    return false;
}

#endif