#ifndef __DWC_TCP_C
#define __DWC_TCP_C

/*--------------------------------------------------------------------------
 *  Copyright (c) 2001-2004 Igor Daniloff. 
 *	All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at this directory.
 *------------------------------------------------------------------------*/
 
#ifndef lint
static const char rcsid[] = "$Id: dwc_tcp.c,v 1.53.6.1 2009/10/07 18:20:47 akorkov Exp $";
#endif

#include <stdio.h>
#include <string.h>

#if defined(__UNIX_DW)
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <fcntl.h>
#include <netdb.h>
#include <errno.h>
#if defined(EMX_DW)
#include <io.h>
#endif /* EMX_DW */
#endif /* __UNIX_DW */

#include "dwc_mutex.h"
#include "dwc_timo.h"
#include "dwc_err.h"
#include "dwc_tcp.h"
#include "dwc_macro.h"
#include "dwc_log.h"
#include "dwc_fd.h"
#include "dwc_lib.h"

#if defined(Solaris_DW)
#ifndef INADDR_NONE
#define INADDR_NONE  -1
#endif /* INADDR_NONE */
#endif /* Solaris */

#ifdef Linux_DW
#define DW_SOL_TCP      SOL_TCP
#elif FreeBSD_DW
#define DW_SOL_TCP      IPPROTO_TCP
#elif Solaris_DW
#define DW_SOL_TCP      IPPROTO_TCP
#else
#error "fill setsockopt(TCP_NODELAY)"
#endif

DWC_mutex_t   resolve_mutex;

/* -------------------------------------------------------------------- */
dwc_bool_t set_tcp_nodelay( DWC_tcp_socket_t sock, dwc_bool_t nodelay )
{
    int one = ( nodelay ? 1 : 0 );
    
    if( setsockopt( sock, DW_SOL_TCP, TCP_NODELAY, &one, sizeof(one) ) < 0 )
    {
        printLib( DW_LL_VERBOSE, "dwlib: cannot set option TCP_NODELAY - %s", stdlib_error(errno) );
        return FALSE;
    }
    return TRUE;
}

/* -------------------------------------------------------------------- */
DWC_string_t tcpsys_error ( void )
{
    return socket_error();
}

/* -------------------------------------------------------------------- */
DWC_error_code_t init_tcp_sockets ( void )
{
#if defined(__WIN32_DW)
    WORD    wVersionRequested;
    WSADATA wsaData;

    wVersionRequested = MAKEWORD( 1, 1 );
    if( WSAStartup( wVersionRequested, &wsaData ) != 0 )
    {
        printLib(DW_LL_ERROR, "dwlib: tcp: cannot inialize WinSock subsystem - %s", tcpsys_error() );
        return DWC_IPCSYSTEM;
    }
    if( (LOBYTE( wsaData.wVersion ) != 1)
        ||
        (HIBYTE( wsaData.wVersion ) != 1) )
    {
        printLib(DW_LL_ERROR, "dwlib: tcp: cannot find WinSock 1.1");
        return DWC_IPCSYSTEM;
    }
#endif /* __WIN32_DW */
    resolve_mutex = dwc_mutex_create( "DwLib::Resolver" );
    if( !resolve_mutex )
    {
#if defined(__WIN32_DW)
        WSACleanup();
#endif /* __WIN32_DW */
        return DWC_NORESOURCE;
    }
    return DWC_NOERROR;
}

/* -------------------------------------------------------------------- */
DWC_error_code_t dinit_tcp_sockets ( void )
{
    DWC_bool_t   error = FALSE;

    if( !dwc_mutex_lock( resolve_mutex ) )
    {
        error = TRUE;
    }
    else
    {
        if( !dwc_mutex_unlock( resolve_mutex ) )
        {
            error = TRUE;
        }
    }
    if( dwc_mutex_destroy( resolve_mutex ) != DWC_NOERROR )
    {
        error = TRUE;
    }
    resolve_mutex = INVALID_MUTEX;
    if( error  )
    {
        printLib( DW_LL_VERBOSE, "dwlib: tcp: some errors during deinit tcp system" );
        return DWC_IPCSYSTEM;
    }
#if defined(__WIN32_DW)
    if( WSACleanup() != 0 )
    {
        printLib( DW_LL_ERROR, "dwlib: tcp: cannot deinitialize WinSock subsystem - %s", tcpsys_error() );
        return DWC_IPCSYSTEM;
    }
#endif /* __WIN32_DW */
    return DWC_NOERROR;
}

/* -------------------------------------------------------------------- */
dwc_bool_t fill_tcp_addr( const char *host, const char *port, DWC_tcp_socket_addr_t *naddr, dwc_bool_t need_resolve )
{
    int             tmp_port;
    struct servent *serv;
    
    EXIT_IF_3PARAM( !host, !port, !naddr, "dwlib: tcp:", return FALSE );    
    memset( naddr, 0, sizeof(*naddr) );
    if( need_resolve && (resolve_tcp_addr( naddr, host, TRUE ) != DWC_NOERROR) )
    {
        printLib( DW_LL_ERROR, "dwlib: tcp: cannot resolve %s hostname", host );
        return FALSE;
    }
    if( sscanf( port, "%u", &tmp_port ) != 1 )
    {
        if( !dwc_mutex_lock( resolve_mutex ) )
        {
            return FALSE;
        }
        serv = getservbyname( port, "tcp" );
        if( serv )
        {
            tmp_port = serv->s_port;
        }
        else
        {
            tmp_port = -1;
        }
        if( !dwc_mutex_unlock( resolve_mutex ) )
        {
            return FALSE;
        }
    }
    if( tmp_port < 0 )
    {
        printLib( DW_LL_ERROR, "dwlib: tcp: invalid port specified" );
        return FALSE;
    }
    naddr->sin_family = AF_INET;
    naddr->sin_port = htons( ((unsigned short)(tmp_port & 0xFFFF)) );
    return TRUE;
}

/* -------------------------------------------------------------------- */
DWC_error_code_t resolve_tcp_addr( DWC_tcp_socket_addr_t* naddr, const char* name, dwc_bool_t do_query )
{
    struct hostent      *he = NULL;
    DWC_error_code_t     error;

    if( !name || (name[0] == NULLCHAR) )
    {
        printLib( DW_LL_ERROR, "dwlib: tcp: empty name passed to resolver" );
        return DWC_BADPARAMS;
    }
#if defined(__WIN32_DW) || defined(Solaris_DW) || defined(EMX_DW)
    naddr->sin_addr.s_addr = inet_addr( name );
    if( naddr->sin_addr.s_addr == INADDR_NONE )
    {
#else /* !(__WIN32_DW || Solaris || EMX) */
    if( inet_aton( name, &naddr->sin_addr ) == 0 )
    {
#endif /* __WIN32_DW || Solaris */
        if( !do_query )
        {       
            printLib(DW_LL_ERROR, "dwlib: tcp: inet_addr(%s) failed", name );
            return DWC_INVALIDADDR;
        }
        if( !dwc_mutex_lock( resolve_mutex ) )
        {
            return DWC_IPCSYSTEM;
        }
        printLib(DW_LL_DEBUG, "dwlib: tcp: call resolver for %s", name);
        he = gethostbyname( name );
        if( he )
        {
            if( (he->h_addrtype == AF_INET)
                && (he->h_addr_list != NULL)
                && (he->h_addr_list[0] != NULL)
                && (he->h_length == sizeof(naddr->sin_addr)) )
            {
                memcpy( &naddr->sin_addr, he->h_addr_list[0], sizeof(naddr->sin_addr) );
                printLib(DW_LL_DEBUG, "dwlib: tcp: resolver return %s", inet_ntoa( naddr->sin_addr ) );
                error = DWC_NOERROR;
            }
            else
            {
                printLib(DW_LL_ERROR, "dwlib: tcp: resolver return corrupted address (%d:%p:%d)", he->h_addrtype, he->h_addr_list, he->h_length );
                error = DWC_INVALIDADDR;
            }
        }
        else
        {
#if defined(__WIN32_DW) || defined(Solaris_DW) || defined(EMX_DW)
            printLib(DW_LL_ERROR,"dwlib: tcp: gethostbyname() error : %s", tcpsys_error() );
#else /* !(__WIN32_DW || Solaris || EMX) */
            printLib(DW_LL_ERROR,"dwlib: tcp: gethostbyname() error : %s", hstrerror(h_errno) );
#endif /*__WIN32_DW || Solaris */
            error = DWC_INVALIDADDR;
        }
        if( !dwc_mutex_unlock( resolve_mutex ) )
        {
            return DWC_IPCSYSTEM;
        }
    }
    else
    {
        naddr->sin_family = AF_INET;
        error = DWC_NOERROR;
    }
    return error;
}

/* -------------------------------------------------------------------- */
DWC_error_code_t create_tcp_socket ( DWC_tcp_socket_t *sock, DWC_bool_t nonblocking )
{
    EXIT_IF_1PARAM( !sock, "dwlib: tcp:", return DWC_IPCSYSTEM );
    *sock = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP );
    if( (*sock) == INVALID_TCP_SOCKET )
    {
        printLib(DW_LL_ERROR,"dwlib: tcp: creation failed - %s", tcpsys_error() );
        return DWC_IPCSYSTEM;
    }
    if( nonblocking && !set_nonblocking_fd(*sock) )
    {
        printLib(DW_LL_ERROR,"dwlib: tcp: switching to nonblocking mode failed - %s", tcpsys_error() );
        close_tcp_socket( sock );
        return DWC_IPCSYSTEM;
    }
    printLib( DW_LL_DEBUG, "dwlib: tcp: socket %d has been created", *sock );
    return DWC_NOERROR;
}

/* -------------------------------------------------------------------- */
DWC_error_code_t bind_tcp_socket ( DWC_tcp_socket_t sock, DWC_tcp_socket_addr_t* addr )
{
    int           opt = 1;
    DWC_socklen_t len = sizeof(DWC_tcp_socket_addr_t);    

    if( setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt) ) < 0 )
    {
        printLib(DW_LL_ERROR, "dwlib: tcp: set reusing of socket failed - %s", tcpsys_error() );
        return DWC_IPCSYSTEM;
    }
    return bind_socket( sock, (struct sockaddr*) addr, len );
}

/* -------------------------------------------------------------------- */
DWC_error_code_t listen_tcp_socket ( DWC_tcp_socket_t sock, int backlog )
{
    return listen_socket( sock, backlog );
}

/* -------------------------------------------------------------------- */
DWC_error_code_t accept_tcp_socket ( DWC_tcp_socket_t sock, DWC_tcp_socket_addr_t *addr, DWC_tcp_socket_t *newsock )
{
    DWC_error_code_t error;
    DWC_socklen_t len = sizeof(DWC_tcp_socket_addr_t);

    error = accept_socket( sock, (struct sockaddr*) addr, &len, newsock );
    if( error != DWC_NOERROR )
    {
        (*newsock) = INVALID_TCP_SOCKET;
    }
    printLib( DW_LL_DEBUG, "dwlib: tcp: socket %d has been accepted", *newsock );
    return DWC_NOERROR;
}

/* -------------------------------------------------------------------- */
DWC_error_code_t connect_tcp_socket ( DWC_tcp_socket_t sock, DWC_tcp_socket_addr_t *addr, DWC_timeout_t timeout )
{
    DWC_error_code_t ret;
    
    printLib(DW_LL_VERBOSE,"dwlib: tcp: connecting to %s:%d ...", inet_ntoa( addr->sin_addr ), ntohs(addr->sin_port) );
    ret = connect_socket( sock, (struct sockaddr*) addr, sizeof(DWC_tcp_socket_addr_t), timeout );
    if( ret != DWC_NOERROR )
    {
        printLib(DW_LL_ERROR,"dwlib: tcp: connecting to %s:%d - failed", inet_ntoa( addr->sin_addr ), ntohs(addr->sin_port) );
    }
    else
    {
        printLib(DW_LL_VERBOSE,"dwlib: tcp: connecting to %s:%d - done", inet_ntoa( addr->sin_addr ), ntohs(addr->sin_port) );
    }        
    return ret;
}

/* -------------------------------------------------------------------- */
DWC_error_code_t send_tcp_socket ( DWC_tcp_socket_t *sock, DWC_pointer_t buf, DWC_length_t *len, DWC_timeout_t timo )
{
    return send_socket( *sock, buf, len, timo );
}

/* -------------------------------------------------------------------- */
DWC_error_code_t recv_tcp_socket ( DWC_tcp_socket_t *sock, DWC_pointer_t buf, DWC_length_t *len, DWC_timeout_t timo )
{
    return recv_socket( *sock, buf, len, timo );
}

/* -------------------------------------------------------------------- */
DWC_error_code_t shutdown_tcp_socket ( DWC_tcp_socket_t *sock )
{
    int error;

    error = shutdown( *sock, SHUT_RDWR );
    (*sock) = INVALID_TCP_SOCKET;
    if( error )
    {
        printLib(DW_LL_ERROR,"dwlib: tcp: shutdown() failed - %s", tcpsys_error() );
        return DWC_IPCSYSTEM;
    }
    return error;
}

/* -------------------------------------------------------------------- */
DWC_error_code_t close_tcp_socket ( DWC_tcp_socket_t *sock )
{
    int error;

    if( *sock == INVALID_TCP_SOCKET )
    {
        printLib(DW_LL_DEBUG,"dwlib: tcp: close() failed - closed socket" );
        return DWC_NOERROR;
    }
    printLib( DW_LL_DEBUG, "dwlib: tcp: socket %d will been closed", *sock );
#if defined(__WIN32_DW)
    error = closesocket( *sock );
#else
    error = close( *sock );
#endif
    *sock = INVALID_TCP_SOCKET;
    if( error )
    {
        printLib(DW_LL_ERROR,"dwlib: tcp: close() failed - %s", tcpsys_error() );
        return DWC_IPCSYSTEM;
    }
    return DWC_NOERROR;
}

#endif /* __DWC_TCP_C */
