Program Listing for File BaseSocket.cpp

Return to documentation for file (src/sockets/BaseSocket.cpp)

//
// Created by Aleksey Timin on 11/18/19.
//

#if defined (__unix__) || defined(__APPLE__)
#include <sys/socket.h>
#include <sys/select.h>
#include <unistd.h>
#elif defined(_WIN32) || defined(WIN32) || defined(_WIN64)
#include <winsock2.h>
#include <time.h>
#endif

#include <utility>
#include <algorithm>

#include "BaseSocket.h"
#include "Platform.h"

namespace eipScanner {
namespace sockets {

    BaseSocket::BaseSocket(EndPoint endPoint)
            : _sockedFd(0)
            , _remoteEndPoint(std::move(endPoint))
            , _recvTimeout(0)
            , _beginReceiveHandler() {

    }

    BaseSocket::BaseSocket(std::string host, int port)
            : BaseSocket(EndPoint(std::move(host), port)) {
    }

    BaseSocket::~BaseSocket() = default;

    void BaseSocket::Shutdown() {
#if defined(_WIN32) || defined(WIN32) || defined(_WIN64)
      shutdown(_sockedFd, SD_BOTH);
#else
      shutdown(_sockedFd, SHUT_RDWR);
#endif
    }

    void BaseSocket::Close() {
#if defined(_WIN32) || defined(WIN32) || defined(_WIN64)
      closesocket(_sockedFd);
#else
      close(_sockedFd);
#endif
    }

    /*static*/int BaseSocket::getLastError() {
#if defined(_WIN32) || defined(WIN32) || defined(_WIN64)
    return WSAGetLastError();
#else
    return errno;
#endif
    }

    /*static*/const std::error_category& BaseSocket::getErrorCategory() noexcept
    {
#if defined(_WIN32) || defined(WIN32) || defined(_WIN64)
    return win32ErrorCategory::category();
#else
    return std::generic_category();
#endif
    }

    int BaseSocket::getSocketFd() const {
        return _sockedFd;
    }

    const std::chrono::milliseconds &BaseSocket::getRecvTimeout() const {
        return _recvTimeout;
    }

    void BaseSocket::setRecvTimeout(const std::chrono::milliseconds &recvTimeout) {
        _recvTimeout = recvTimeout;

#if defined(_WIN32) || defined(WIN32) || defined(_WIN64)
                uint32_t ms = recvTimeout.count();
                setsockopt(_sockedFd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&ms, sizeof ms);
#else
                timeval tv = makePortableInterval(recvTimeout);
                setsockopt(_sockedFd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv);
#endif

    }

    timeval BaseSocket::makePortableInterval(const std::chrono::milliseconds &recvTimeout) {
        struct timeval tv = {

#ifdef __APPLE__
        .tv_sec = static_cast<__darwin_suseconds_t>(recvTimeout.count()/1000),
        .tv_usec =  static_cast<__darwin_suseconds_t>((recvTimeout.count()%1000)*1000)
#elif __unix__
        .tv_sec = static_cast<__time_t>(recvTimeout.count()/1000),
        .tv_usec =  static_cast<__time_t>((recvTimeout.count()%1000)*1000)
#elif defined(_WIN32) || defined(WIN32) || defined(_WIN64)
        .tv_sec = static_cast<long int>(recvTimeout.count()/1000),
        .tv_usec =  static_cast<long int>((recvTimeout.count()%1000)*1000)
#endif

        };

        tv.tv_sec = std::max<decltype(tv.tv_sec)>(tv.tv_sec, 0);
    tv.tv_usec = std::max<decltype(tv.tv_usec)>(tv.tv_usec, 0);
        return tv;
    }

    void BaseSocket::setBeginReceiveHandler(BaseSocket::BeginReceiveHandler handler) {
        _beginReceiveHandler = std::move(handler);
    }

    void BaseSocket::BeginReceive() {
        _beginReceiveHandler(*this);
    }

    void BaseSocket::select(std::vector<BaseSocket::SPtr> sockets, std::chrono::milliseconds timeout) {
        BaseSocket::SPtr socketWithMaxFd = *std::max_element(sockets.begin(), sockets.end(), [](auto sock1, auto sock2) {
            return sock1->getSocketFd() < sock2->getSocketFd();
        });

        auto startTime = std::chrono::steady_clock::now();
        auto stopTime = startTime + timeout;
        int ready;
        do {
            timeval tv = makePortableInterval(std::chrono::duration_cast<std::chrono::milliseconds>(stopTime-startTime));

            fd_set recvSet;
            FD_ZERO(&recvSet);
            for (auto& sock : sockets) {
                FD_SET(sock->getSocketFd(), &recvSet);
            }

            ready = ::select(socketWithMaxFd->getSocketFd() + 1, &recvSet, NULL, NULL, &tv);
            if (ready < 0) {
                throw std::system_error(BaseSocket::getLastError(), BaseSocket::getErrorCategory());
            }

            for (auto& sock : sockets) {
                if (FD_ISSET(sock->getSocketFd(), &recvSet)) {
                    sock->BeginReceive();
                    continue;
                }
            }

            startTime = std::chrono::steady_clock::now();
        } while(ready > 0);
    }

    const EndPoint &BaseSocket::getRemoteEndPoint() const {
        return _remoteEndPoint;
    }

}
}