/**
 * xrdp: A Remote Desktop Protocol server.
 *
 * Copyright (C) Jay Sorg 2013
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "gtcp.h"

/**
 * Return a newly created socket or -1 on error
 *****************************************************************************/

int tcp_socket_create(void)
{
    int rv;
    int option_value;
    socklen_t option_len;

    /* in win32 a socket is an unsigned int, in linux, it's an int */
    if ((rv = (int) socket(PF_INET, SOCK_STREAM, 0)) < 0)
    {
        return -1;
    }

    option_len = sizeof(option_value);

    if (getsockopt(rv, SOL_SOCKET, SO_REUSEADDR, (char *) &option_value,
                   &option_len) == 0)
    {
        if (option_value == 0)
        {
            option_value = 1;
            option_len = sizeof(option_value);
            setsockopt(rv, SOL_SOCKET, SO_REUSEADDR, (char *) &option_value,
                       option_len);
        }
    }

    option_len = sizeof(option_value);

    if (getsockopt(rv, SOL_SOCKET, SO_SNDBUF, (char *) &option_value,
                   &option_len) == 0)
    {
        if (option_value < (1024 * 32))
        {
            option_value = 1024 * 32;
            option_len = sizeof(option_value);
            setsockopt(rv, SOL_SOCKET, SO_SNDBUF, (char *) &option_value,
                       option_len);
        }
    }

    return rv;
}

/**
 * Place specified socket in non blocking mode
 *****************************************************************************/

void tcp_set_non_blocking(int skt)
{
    unsigned long i;

#if defined(_WIN32)
    i = 1;
    ioctlsocket(skt, FIONBIO, &i);
#else
    i = fcntl(skt, F_GETFL);
    i = i | O_NONBLOCK;
    fcntl(skt, F_SETFL, i);
#endif
}

/**
 * Assign name to socket
 *
 * @param  skt  the socket to bind
 * @param  port  the port to bind to
 *
 * @return 0 on success, -1 on error
 *****************************************************************************/

int tcp_bind(int skt, char *port)
{
    struct sockaddr_in s;

    memset(&s, 0, sizeof(struct sockaddr_in));
    s.sin_family = AF_INET;
    s.sin_port = htons((uint16_t) atoi(port));
    s.sin_addr.s_addr = INADDR_ANY;

    return bind(skt, (struct sockaddr *) &s, sizeof(struct sockaddr_in));
}

/**
 * Listen for incoming connections
 *
 * @param  skt  the socket to listen on
 *
 * @return 0 on success, -1 on error
 *****************************************************************************/

int tcp_listen(int skt)
{
    return listen(skt, 2);
}

/**
 * Accept incoming connection
 *
 * @param  skt  socket to accept incoming connection on
 *
 * @return 0 on success, -1 on error
 *****************************************************************************/

int tcp_accept(int skt)
{
    int ret ;
    char ipAddr[256] ;
    struct sockaddr_in s;
    socklen_t i;

    i = sizeof(struct sockaddr_in);
    memset(&s, 0, i);
    return accept(skt, (struct sockaddr *)&s, &i);
}

/**
 * Check if the socket would block
 *
 * @return TRUE if would block, else FALSE
 *****************************************************************************/

int tcp_last_error_would_block()
{
#if defined(_WIN32)
    return WSAGetLastError() == WSAEWOULDBLOCK;
#else
    return (errno == EWOULDBLOCK) || (errno == EAGAIN) || (errno == EINPROGRESS);
#endif
}

/**
 * Close specified socket
 *****************************************************************************/

void tcp_close(int skt)
{
    if (skt <= 0)
    {
        return;
    }

#if defined(_WIN32)
    closesocket(skt);
#else
    close(skt);
#endif
}

/**
 * Create a new socket
 *
 * @return new socket or -1 on error
 *****************************************************************************/

int tcp_socket(void)
{
    int rv;
    int option_value;
    socklen_t option_len;

    /* in win32 a socket is an unsigned int, in linux, it's an int */
    if ((rv = (int) socket(PF_INET, SOCK_STREAM, 0)) < 0)
    {
        return -1;
    }

    option_len = sizeof(option_value);

    if (getsockopt(rv, SOL_SOCKET, SO_REUSEADDR, (char *) &option_value,
                   &option_len) == 0)
    {
        if (option_value == 0)
        {
            option_value = 1;
            option_len = sizeof(option_value);
            setsockopt(rv, SOL_SOCKET, SO_REUSEADDR, (char *) &option_value,
                       option_len);
        }
    }

    option_len = sizeof(option_value);

    if (getsockopt(rv, SOL_SOCKET, SO_SNDBUF, (char *) &option_value,
                   &option_len) == 0)
    {
        if (option_value < (1024 * 32))
        {
            option_value = 1024 * 32;
            option_len = sizeof(option_value);
            setsockopt(rv, SOL_SOCKET, SO_SNDBUF, (char *) &option_value,
                       option_len);
        }
    }

    return rv;
}

/**
 * Connect to a server
 *
 * @param  skt      opaque socket obj
 * @param  address  connect to this server
 * @param  port     using this port
 *
 * @return 0 on success, -1 on error
 *****************************************************************************/

int tcp_connect(int skt, const char *hostname, const char *port)
{
    struct sockaddr_in  s;
    struct hostent     *h;

    memset(&s, 0, sizeof(struct sockaddr_in));
    s.sin_family = AF_INET;
    s.sin_port = htons((uint16_t) atoi(port));
    s.sin_addr.s_addr = inet_addr(hostname);

    if (s.sin_addr.s_addr == INADDR_NONE)
    {
        h = gethostbyname(hostname);

        if (h != 0)
        {
            if (h->h_name != 0)
            {
                if (h->h_addr_list != 0)
                {
                    if ((*(h->h_addr_list)) != 0)
                    {
                        s.sin_addr.s_addr = *((int *)(*(h->h_addr_list)));
                    }
                }
            }
        }
    }

    return connect(skt, (struct sockaddr *) &s, sizeof(struct sockaddr_in));
}

/**
 * Return 1 if we can write to the socket, 0 otherwise
 *****************************************************************************/

int tcp_can_send(int skt, int millis)
{
    fd_set         wfds;
    struct timeval time;
    int            rv;

    time.tv_sec = millis / 1000;
    time.tv_usec = (millis * 1000) % 1000000;
    FD_ZERO(&wfds);

    if (skt > 0)
    {
        FD_SET(((unsigned int) skt), &wfds);
        rv = select(skt + 1, 0, &wfds, 0, &time);

        if (rv > 0)
        {
            return tcp_socket_ok(skt);
        }
    }

    return 0;
}

/**
 * Return 1 if socket is OK, 0 otherwise
 *****************************************************************************/

int tcp_socket_ok(int skt)
{
    int opt;
    socklen_t opt_len = sizeof(opt);

    if (getsockopt(skt, SOL_SOCKET, SO_ERROR, (char *) (&opt), &opt_len) == 0)
    {
        if (opt == 0)
        {
            return 1;
        }
    }

    return 0;
}

/**
 * Check if specified sockets can be operated on without blocking
 *
 * @return 1 if they can be operated on or 0 if blocking would occur
 *****************************************************************************/

int tcp_select(int sck1, int sck2)
{
    fd_set         rfds;
    struct timeval time;

    int            max = 0;
    int            rv  = 0;

    memset(&rfds, 0, sizeof(fd_set));
    memset(&time, 0, sizeof(struct timeval));

    time.tv_sec = 0;
    time.tv_usec = 0;
    FD_ZERO(&rfds);

    if (sck1 > 0)
    {
        FD_SET(((unsigned int) sck1), &rfds);
    }

    if (sck2 > 0)
    {
        FD_SET(((unsigned int) sck2), &rfds);
    }

    max = sck1;

    if (sck2 > max)
    {
        max = sck2;
    }

    rv = select(max + 1, &rfds, 0, 0, &time);

    if (rv > 0)
    {
        rv = 0;

        if (FD_ISSET(((unsigned int) sck1), &rfds))
        {
            rv = rv | 1;
        }

        if (FD_ISSET(((unsigned int)sck2), &rfds))
        {
            rv = rv | 2;
        }
    }
    else
    {
        rv = 0;
    }

    return rv;
}

int tcp_recv(int skt, void *ptr, int len, int flags)
{
#if defined(_WIN32)
    return recv(skt, (char *) ptr, len, flags);
#else
    return recv(skt, ptr, len, flags);
#endif
}

int tcp_send(int skt, const void *ptr, int len, int flags)
{
#if defined(_WIN32)
    return send(skt, (const char *)ptr, len, flags);
#else
    return send(skt, ptr, len, flags);
#endif
}
