
/*---------------------------------------------------------------------------*/
/* client_sockets.cpp                                                        */
/* copyright (c) innovaphone 2015                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#include "platform/platform.h"

#include <signal.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#ifdef _IOS_
#include <netdb.h>
#include <sys/sockio.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <sys/ioctl.h>
#include <ifaddrs.h>
#include <net/ethernet.h>
#include <sys/types.h>
#else
#ifndef SOCK_NONBLOCK
#include <asm/ioctls.h>
#endif
#endif

#include "common/os/iomux.h"
#include "client/linux/os-cpp/iomux.h"
#include "common/interface/socket.h"
#include "common/linux/udp/udpsocket.h"
#include "common/linux/tcp/tcpsocket.h"
#include "common/linux/uds/udsocket.h"
#include "client_sockets.h"

#define SOCKET_ACTIVE_TIMEOUT  3

/*---------------------------------------------------------------------------*/
/* class ClientSocketProvider	                                             */
/*---------------------------------------------------------------------------*/

class ClientSockets : public UClientSockets
{
    class IClientSockets * sockets;
    int providerCount;
    int rtpSocketCount;
    int multicastReceiveCount;
    int socketActiveCounter;

    void ClientSocketsTimeout() override;
public:
    ClientSockets();
    ~ClientSockets();

    void RegisterProvider(IIoMux * const iomux);
    void UnregisterProvider();
    void SocketActive();
    void RegisterRtpSocketActive();
    void UnregisterRtpSocketActive();
    void RegisterMulticastReceiveActive();
    void UnregisterMulticastReceiveActive();
};

ClientSockets::ClientSockets()
{
    providerCount = 0;
    rtpSocketCount = 0;
    multicastReceiveCount = 0;
    socketActiveCounter = 0;
}

ClientSockets::~ClientSockets()
{
}

void ClientSockets::ClientSocketsTimeout()
{
    if(socketActiveCounter != 0) {
        socketActiveCounter--;
        if(socketActiveCounter != 0) {
            sockets->StartTimer(1000);
        }
        else {
            if(rtpSocketCount == 0) {
                sockets->SocketsActive(false);
            }
        }
    }
}

void ClientSockets::RegisterProvider(IIoMux * const iomux)
{
    if (providerCount == 0) {
        sockets = IClientSockets::Create(iomux, this);
    }
    providerCount++;
}

void ClientSockets::UnregisterProvider()
{
    providerCount--;
    if (providerCount == 0) {
        if(socketActiveCounter != 0) {
            socketActiveCounter = 0;
            sockets->CancelTimer();
            sockets->SocketsActive(false);
        }
        delete sockets;
    }
}

void ClientSockets::SocketActive()
{
    if(socketActiveCounter < SOCKET_ACTIVE_TIMEOUT) {
        if(socketActiveCounter == 0) {
            if(rtpSocketCount == 0) {
                sockets->SocketsActive(true);
            }
            sockets->StartTimer(1000);
        }
        socketActiveCounter = SOCKET_ACTIVE_TIMEOUT;
    }
}

void ClientSockets::RegisterRtpSocketActive()
{
    if(rtpSocketCount == 0) {
        sockets->RtpSocketsActive(true);
        if(socketActiveCounter == 0) {
            sockets->SocketsActive(true);
        }
    }
    rtpSocketCount++;
}

void ClientSockets::UnregisterRtpSocketActive()
{
    rtpSocketCount--;
    if(rtpSocketCount == 0) {
        sockets->RtpSocketsActive(false);
        if(socketActiveCounter == 0) {
            sockets->SocketsActive(false);
        }
    }
}

void ClientSockets::RegisterMulticastReceiveActive()
{
    if(multicastReceiveCount == 0) {
        sockets->MulticastReceiveActive(true);
    }
    multicastReceiveCount++;
}

void ClientSockets::UnregisterMulticastReceiveActive()
{
    multicastReceiveCount--;
    if (multicastReceiveCount == 0) {
        sockets->MulticastReceiveActive(false);
    }
}

static class ClientSockets clientSockets;

/*---------------------------------------------------------------------------*/
/* class ClientUDPSocketProvider	                                         */
/*---------------------------------------------------------------------------*/

class UDPSocket * ClientUDPSocketProvider::NewSocket(IIoMux * const iomux, USocket * const user, class IInstanceLog * const log)
{
    if (!registered) {
        registered = true;
        clientSockets.RegisterProvider(iomux);
    }
    ClientUDPSocket * tmp = new ClientUDPSocket(iomux, user, log, this);
    sockets.push_back(tmp);
    return tmp;
}

void ClientUDPSocketProvider::CloseListen()
{
    ClientUDPSocket *s;
    if(!sockets.empty()) {
        for(s = sockets.front(); s; s = s->goNext()) {
            s->CloseListen();
        }
    }
}

void ClientUDPSocketProvider::RestartListen()
{
    ClientUDPSocket *s;
    if(!sockets.empty()) {
        for(s = sockets.front(); s; s = s->goNext()) {
            s->RestartListen();
        }
    }
}

ClientUDPSocketProvider::ClientUDPSocketProvider()
{
    registered = false;
}

ClientUDPSocketProvider::~ClientUDPSocketProvider()
{
    if (registered) {
        clientSockets.UnregisterProvider();
    }
}

ClientUDPSocketProvider * ClientCreateUDPSocketProvider()
{
    return new ClientUDPSocketProvider;
}

/*---------------------------------------------------------------------------*/
/* class ClientUDPSocket                                                     */
/*---------------------------------------------------------------------------*/

ClientUDPSocket::ClientUDPSocket(IIoMux * iomux, USocket * const user, class IInstanceLog * const log, ClientUDPSocketProvider * provider) :
    UDPSocket(iomux, user, log, provider)
{
    rtpSocketActive = false;
    multicastReceiveActive = false;
}

ClientUDPSocket::~ClientUDPSocket()
{
    if(rtpSocketActive) {
        clientSockets.UnregisterRtpSocketActive();
        rtpSocketActive = false;
    }
    if (multicastReceiveActive) {
        clientSockets.UnregisterMulticastReceiveActive();
        multicastReceiveActive = false;
    }
}

void ClientUDPSocket::UserSocketBindResult(const char * localAddr, word localPort)
{
    if (localPort != 0) {
        if(!rtpSocketActive/* && (type == SOCKET_RTP)*/) {
            rtpSocketActive = true;
            clientSockets.RegisterRtpSocketActive();
        }
        if (!multicastReceiveActive) {
            multicastReceiveActive = useIPv6 ?
                ((((sockaddr_in6*)&localAddr)->sin6_addr.s6_addr[0] == 0xff) ||
                 ((((sockaddr_in6*)&localAddr)->sin6_addr.s6_addr[0] == 0x00) &&
                  (((sockaddr_in6*)&localAddr)->sin6_addr.s6_addr[1] == 0x00) &&
                  (((sockaddr_in6*)&localAddr)->sin6_addr.s6_addr[2] == 0x00) &&
                  (((sockaddr_in6*)&localAddr)->sin6_addr.s6_addr[3] == 0x00) &&
                  (((sockaddr_in6*)&localAddr)->sin6_addr.s6_addr[4] == 0x00) &&
                  (((sockaddr_in6*)&localAddr)->sin6_addr.s6_addr[5] == 0x00) &&
                  (((sockaddr_in6*)&localAddr)->sin6_addr.s6_addr[6] == 0x00) &&
                  (((sockaddr_in6*)&localAddr)->sin6_addr.s6_addr[7] == 0x00) &&
                  (((sockaddr_in6*)&localAddr)->sin6_addr.s6_addr[8] == 0x00) &&
                  (((sockaddr_in6*)&localAddr)->sin6_addr.s6_addr[9] == 0x00) &&
                  (((sockaddr_in6*)&localAddr)->sin6_addr.s6_addr[10] == 0xff) &&
                  (((sockaddr_in6*)&localAddr)->sin6_addr.s6_addr[11] == 0xff) &&
                  ((((sockaddr_in6*)&localAddr)->sin6_addr.s6_addr[12] & 0xf0) == 0xe0))) :
                ((((byte *)(&(((sockaddr_in*)&localAddr)->sin_addr)))[0] & 0xf0) == 0xe0);
            if(multicastReceiveActive) {
                clientSockets.RegisterMulticastReceiveActive();
            }
        }
    }
    UDPSocket::UserSocketBindResult(localAddr, localPort);
}

void ClientUDPSocket::UserSocketShutdown(shutdownreason_t reason)
{
    if(rtpSocketActive) {
        clientSockets.UnregisterRtpSocketActive();
        rtpSocketActive = false;
    }
    if (multicastReceiveActive) {
        clientSockets.UnregisterMulticastReceiveActive();
        multicastReceiveActive = false;
    }
    UDPSocket::UserSocketShutdown(reason);
}

void ClientUDPSocket::CloseListen()
{
    if ((socketStatus == SST_BIND_START) || (socketStatus == SST_BIND_DONE) || (socketStatus == SST_CONNECTION_INITIATED) || (socketStatus == SST_CONNECTED)) {
        shutdown(FDIIoMux, SHUT_RDWR);
        iomux->FDClose(this);
    }
}

void ClientUDPSocket::RestartListen()
{
    if ((socketStatus == SST_BIND_START) || (socketStatus == SST_BIND_DONE) || (socketStatus == SST_CONNECTION_INITIATED) || (socketStatus == SST_CONNECTED)) {
        int restoreStatus = socketStatus;
        socketStatus = SST_CLOSED;
        OpenSocket(useIPv6);
        if (useIPv6) {
            int res = bind(FDIIoMux, (struct sockaddr *)(&localAddr), sizeof(sockaddr_in6));
            if (res != 0) {
                log->Log(LOG_UDP, "ClientUDPSocket(%08x,%08x)::BindIPv6 bind() failed (%u : %s)", (UDPSocket *)this, user, errno, strerror(errno));
            }
        }
        else {
            int res = bind(FDIIoMux, (struct sockaddr *)(&localAddr), sizeof(sockaddr_in));
            if (res != 0) {
                log->Log(LOG_UDP, "ClientUDPSocket(%08x,%08x)::BindIPv4 bind() failed (%u : %s)", (UDPSocket *)this, user, errno, strerror(errno));
            }
        }
        iomux->FDAdd(FDIIoMux, this, false);
        if ((restoreStatus == SST_CONNECTION_INITIATED) || (restoreStatus == SST_CONNECTED)) {
            if (useIPv6) {
                int res = connect(FDIIoMux, (struct sockaddr *)(&connectedAddr), sizeof(sockaddr_in6));
                if (res < 0 && errno != EINPROGRESS) {
                    log->Log(LOG_UDP, "ClientUDPSocket(%08x,%08x)::ConnectIPv6 connect() failed (%u : %s)", (UDPSocket *)this, user, errno, strerror(errno));
                }
            }
            else {
                int res = connect(FDIIoMux, (struct sockaddr *)(&connectedAddr), sizeof(sockaddr_in));
                if (res < 0 && errno != EINPROGRESS) {
                    log->Log(LOG_UDP, "ClientUDPSocket(%08x,%08x)::ConnectIPv4 connect() failed (%u : %s)", (UDPSocket *)this, user, errno, strerror(errno));
                }
            }
        }
        socketStatus = restoreStatus;
    }
}

/*---------------------------------------------------------------------------*/
/* class ClientTCPSocketProvider                                             */
/*---------------------------------------------------------------------------*/

class TCPSocket * ClientTCPSocketProvider::NewSocket(IIoMux * const iomux, USocket * const user, class IInstanceLog * const log)
{
    if (!registered) {
        registered = true;
        clientSockets.RegisterProvider(iomux);
    }
    ClientTCPSocket * tmp = new ClientTCPSocket(iomux, user, log, this);
    sockets.push_back(tmp);
    return tmp;
}

void ClientTCPSocketProvider::CloseListen()
{
    ClientTCPSocket *s;
    if(!sockets.empty()) {
        for(s = sockets.front(); s; s = s->goNext()) {
            s->CloseListen();
        }
    }
}

void ClientTCPSocketProvider::RestartListen()
{
    ClientTCPSocket *s;
    if(!sockets.empty()) {
        for(s = sockets.front(); s; s = s->goNext()) {
            s->RestartListen();
        }
    }
}

void ClientTCPSocketProvider::CloseSockets()
{
    ClientTCPSocket *s, *next;
    if(!sockets.empty()) {
        for(s = sockets.front(); s; s = next) {
            next = s->goNext();
            s->CloseSockets();
        }
    }
}

ClientTCPSocketProvider::ClientTCPSocketProvider()
{
    registered = false;
}

ClientTCPSocketProvider::~ClientTCPSocketProvider()
{
    if (registered) {
        clientSockets.UnregisterProvider();
    }
}

ClientTCPSocketProvider * ClientCreateTCPSocketProvider()
{
    return new ClientTCPSocketProvider;
}

/*---------------------------------------------------------------------------*/
/* class ClientTCPSocket                                                     */
/*---------------------------------------------------------------------------*/

ClientTCPSocket::ClientTCPSocket(IIoMux * iomux, USocket * const user, class IInstanceLog * const log, ClientTCPSocketProvider * provider) :
    TCPSocket(iomux, user, log, provider)
{
}

ClientTCPSocket::~ClientTCPSocket()
{
}

void ClientTCPSocket::FDNotify(bool notifyRX, bool notifyTX)
{
    if (notifyRX) {
        clientSockets.SocketActive();
    }
    (static_cast<IIoMuxEx *>(iomux))->FDNotify(this, notifyRX, notifyTX);
}

void ClientTCPSocket::CloseListen()
{
    if ((socketStatus == SST_BIND_START) || (socketStatus == SST_BIND_DONE) || (socketStatus == SST_LISTEN)) {
        shutdown(FDIIoMux, SHUT_RDWR);
        iomux->FDClose(this);
    }
}

void ClientTCPSocket::RestartListen()
{
    if ((socketStatus == SST_BIND_START) || (socketStatus == SST_BIND_DONE) || (socketStatus == SST_LISTEN)) {
        int res;
        int restoreStatus = socketStatus;
        socketStatus = SST_CLOSED;
        OpenSocket(useIPv6);
        // It will be good to allow the socket to reuse the address.
        int r = 1;
        if (setsockopt(FDIIoMux, SOL_SOCKET, SO_REUSEADDR, &r, sizeof(r)) != 0) {
            log->Log(LOG_TCP, "ClientTCPSocket(%08x,%08x)::Bind setsockopt() failed (%u : %s)", (TCPSocket *)this, user, errno, strerror(errno));
        }
#ifdef SO_REUSEPORT
        setsockopt(FDIIoMux, SOL_SOCKET, SO_REUSEPORT, &r, sizeof(r));
#endif
        if (useIPv6) {
            res = bind(FDIIoMux, (struct sockaddr *)(&localAddr), sizeof(sockaddr_in6));
            if (res != 0) {
                log->Log(LOG_TCP, "ClientTCPSocket(%08x,%08x)::BindIPv6 bind() failed (%u : %s)", (TCPSocket *)this, user, errno, strerror(errno));
            }
        }
        else {
            res = bind(FDIIoMux, (struct sockaddr *)(&localAddr), sizeof(sockaddr_in));
            if (res != 0) {
                log->Log(LOG_TCP, "ClientTCPSocket(%08x,%08x)::BindIPv4 bind() failed (%u : %s)", (TCPSocket *)this, user, errno, strerror(errno));
            }
        }
        if ((restoreStatus == SST_LISTEN) && (res == 0)) {
            if (listen(FDIIoMux, SOMAXCONN) != 0) {
                log->Log(LOG_TCP, "ClientTCPSocket(%08x,%08x)::Listen listen() failed (%u : %s)", (TCPSocket *)this, user, errno, strerror(errno));
            }
            iomux->FDAdd(FDIIoMux, this, true);
        }
        socketStatus = restoreStatus;
    }
}

void ClientTCPSocket::CloseSockets()
{
    if (close) {
        if (!isServer && (socketStatus == SST_CONNECTED)) {
            DoShutdown();
        }
    }
}

/*---------------------------------------------------------------------------*/
/* class ClientUDSocketProvider                                              */
/*---------------------------------------------------------------------------*/

class UDSocket * ClientUDSocketProvider::NewSocket(IIoMux * const iomux, USocket * const user, class IInstanceLog * const log)
{
    return new ClientUDSocket(iomux, user, log, this);
}

ClientUDSocketProvider * ClientCreateLocalSocketProvider()
{
    return new ClientUDSocketProvider;
}

/*---------------------------------------------------------------------------*/
/* class ClientUDSocket                                                     */
/*---------------------------------------------------------------------------*/

ClientUDSocket::ClientUDSocket(IIoMux * iomux, USocket * const user, class IInstanceLog * const log, ClientUDSocketProvider * provider) :
    UDSocket(iomux, user, log, provider)
{
}

void ClientUDSocket::FDNotify(bool notifyRX, bool notifyTX)
{
    (static_cast<IIoMuxEx *>(iomux))->FDNotify(this, notifyRX, notifyTX);
}

/*---------------------------------------------------------------------------*/
