/*
 vservtst.c - interactive VRML net java server test

 Copyright (C) 2002    Peter Graf

 This file is part of VRMLNETJ - The VRML net java server.
 VRMLNETJ is free software.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

   For more information on the Program Base Library or Peter Graf,
   please see: http://www.mission-base.com/.

    $Log: vservtst.c,v $


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

/* 
 * make sure "strings <exe> | grep Id | sort -u" shows the source file versions
 */
static char * rcsid = "$Id: vservtst.c,v 1.2 2002/09/12 20:57:15 peter Exp $";
static int    rcsid_fkt() { return( rcsid ? 0 : rcsid_fkt() ); }

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <ctype.h>

#ifdef _WIN32

#include <winsock.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <malloc.h>
#include <errno.h>
#include <ctype.h>
#include <time.h>
#include <sys/types.h>

#define socket_close closesocket
#define PACKET_ERRNO            WSAGetLastError()
#define PACKET_EINTR            WSAEINTR

#else /* UNIX */

#define socket_close close
#define PACKET_ERRNO            errno
#define PACKET_EINTR            EINTR

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <malloc.h>
#include <errno.h>
#include <time.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#endif

#define NJS_ISAMTEST_BUFLEN              2048

static FILE * logfile;
static FILE * infile;

static void njssay();

static void putChar( int c )
{
   static int last = 0;

   if( last == '\n' && c == '\n' )
   {
       return;
   }

   last = c;
   putc( last, logfile );
}

static int getChar( void )
{
    int c;
    c = getc( infile );

    /*
     * a '#' starts a comment for the rest of the line
     */
    if( c == '#')
    {
        /*
         * comments starting with ## are duplicated to the output
         */
        c = getc( infile );
        if( c == '#' )
        {
            putChar( '#' );
            putChar( '#' );

            while( c != '\n' && c != EOF )
            {   
                c = getc( infile );
                if( c != EOF )
                {
                    putChar( c );
                }
            }
        }
        else
        {
            while( c != '\n' && c != EOF )
            {
                c = getc( infile );
            }
        }
    }

    if( c != EOF )
    {
        putChar( c );
    }

    return( c );
}

static void getWord( char * buffer )
{
    int c;
    int i;

    /*
     * skip preceeding blanks
     */
    c = ' ';
    while( c == '\t' || c == ' ' || c == '\n' || c == '\r' )
    {
        c = getChar();
    }

    /*
     * read one word
     */
    for( i = 0; i < NJS_ISAMTEST_BUFLEN - 1; i++, c = getChar() )
    {
        if( c == EOF )
        {
            exit( 0 );
        }


        if( c == '\r' )
        {
            continue;
        }

        if( c == '\t' || c == ' ' || c == '\n' || c == '\r' )
        {
            *buffer = '\0';
            return;
        }

        *buffer++ = c;
    }

    *buffer = '\0';
}

static int my_gethostbyname(
char *  phostname,                      /* r  hostname */
unsigned long *  pip                    /* w  ip addess in host format */
)
/*  =DocEnd= */
{
  /* LOCAL DATA */
    struct hostent     *hostinfo = NULL;  /* hostinfo of Server        */
    int                 i;

    /*
     * NT reports an error in gethostbyname when an dotted ip address is
     * given as parameter, hard to believe.
     */
    if( isdigit( *phostname ))
    {
        *pip = inet_addr( phostname );
        if( *pip )
        {
            *pip = ntohl( *pip );
            return( 0 );
        }
    }

    /*
     * call the host name lookup function in a loop,
     * it might fail of just called once
     */
    for( i = 0; i < 10; i++ )
    {
        /* 
         * find out internet address of the host
         */
        hostinfo = gethostbyname( phostname );
        if( !hostinfo )
        {
            /*
             * if the error number tells us to try again we do so 
             */
#ifdef _WIN32
            if( WSAGetLastError( ) == WSATRY_AGAIN )
            {   
                continue;
            }
#else 
            if( h_errno == TRY_AGAIN )
            {
                continue;
            }
#endif
        }

        /*
         * either way, give it up
         */
        break;
    }

    if( ! hostinfo ) 
    {
        return( -1 );
    }

    memcpy( pip, hostinfo->h_addr, sizeof(unsigned long) );
    *pip = ntohl( *pip );

    return( 0 );
             
}

static int srvadr_byip (
unsigned long pip,                      /* r  of host providing the service */
short         pport,                    /* r  of the service required       */
char      **  ppsrvadr                  /*  w address of server             */
)
/*  =DocEnd= */
{
  /* LOCAL DATA */
    struct sockaddr_in *serv_addr; /* address of server                 */

    /*
     * malloc space for the server address structure
     */
    serv_addr = malloc( sizeof( struct sockaddr_in ));
    if( !serv_addr )
    {
        return( -1 );
    }

    /*
     * set internet address for server
     */
    serv_addr->sin_family      = AF_INET;
    serv_addr->sin_port        = pport;
    memcpy( &(serv_addr->sin_addr.s_addr),
            &pip,
            sizeof( serv_addr->sin_addr.s_addr )
          );

    *ppsrvadr = ( char * )serv_addr;
    return( sizeof( struct sockaddr_in ) );

}

static int srvadr_get (
char *  phostname,                      /* r  of host providing the service */
short   pport,                          /* r  of the service required       */
char ** ppsrvadr                        /*  w address of server             */
)
/*  =DocEnd= */
{
    unsigned long       hostip = 0;
    int                 rc = -1;

    /*
     * look up the hostname supplied by the caller
     */
    rc = my_gethostbyname( phostname, &hostip );
    if( rc )
    {
        return( rc );
    }

    /*
     * use the IP we got
     */
    return( srvadr_byip( htonl(hostip), htons(pport), ppsrvadr ));
             
}

int   packet_read(
int              psocket,                    /* r  The socket to use        */
char            *pbuf,                       /* w  Buffer to read to        */
int              pbufsize,                   /* r  Maximum size of buffer   */
char            *pfrom,                      /* w  address of sender        */
int             *pfromlen,                   /* w  length of that addres    */
struct timeval  *ptimeout                    /* r  Timeout for select call  */
)
/*  =DocEnd= */
{
    int     rc;                         /* return code of select              */
    fd_set  fdvar;                      /* bit array for select call          */
    int     socketerror = 0;
    int     optlen = sizeof( int );
	int     myErrno = 0;

    /*
     * the socket must not be negative
     */
    if( psocket < 0 )
    {
        return ( -1 );
    }

    /*
     * we clear any error condition on the socket first
     */
    optlen = sizeof( socketerror );
    if( getsockopt( psocket, SOL_SOCKET, SO_ERROR,
                    (char*)&socketerror, &optlen ))
    {
    }

    /*
     * give out the error message if there was one
     */
    if( socketerror )
    {
    }

    /*
     * loop until either a timeout or a hard error occurs, or until we
     * we read data
     */
    while( 1 )
    {
        /*
         * we use select to see whether there is data available on the
         * socket or to receive a timeout
         */
        FD_ZERO( &fdvar );                     /* clear the descriptor bitmap */
        FD_SET( psocket, &fdvar );             /* only set our socket         */
        rc = select( psocket + 1,      /* the highest socket to check         */
                     &fdvar,           /* check our socket for reading        */
                     ( fd_set * ) 0,   /* not interested in write sockets     */
                     ( fd_set * ) 0,   /* not interested in OOB sockets       */
                     ptimeout
                   );

        switch( rc )
        {
          case 0:
            /*
             * a timeout occurred, set the answer accordingly
             */
            return ( 0 );

          case -1:
            /*
             * See whether a real error or an interrupt
             */
            myErrno = PACKET_ERRNO;
            if( myErrno == PACKET_EINTR )
            {
                return( 0 );
            }

            /*
             * an error occured during the select
             */
            return ( -1 );

          default:
            /*
             * we clear any error condition on the socket first
             */
            optlen = sizeof( socketerror );
            if( getsockopt( psocket, SOL_SOCKET, SO_ERROR,
                            ( char *)&socketerror, &optlen ))
            {
                return( -1 );
            }

            /*
             * give out the error message if there was one
             */
            if( socketerror )
            {
                /*
                 * the select most likely only came back because there was
                 * an error condition on the socket, therefore we do it
                 * again
                 */
                continue;
            }

            /*
             * data is ready to be read at the socket, thus read it
             */

            /*
             * receive from any peer
             */
            memset( pfrom, 0, *pfromlen );
            rc = recvfrom( psocket, pbuf, pbufsize, 0,
                           ( struct sockaddr * )pfrom, pfromlen );

            if( rc < 0 )
            {
				myErrno = PACKET_ERRNO;

                /*
                 * if we woke up because of a signal
                 */
                if( myErrno == PACKET_EINTR )
                {
                    return( 0 );
                }

                return( -1 );
            }

            /*
             * we got some data, return the length of it
             */
            return( rc );
        }
    }

    return( 0 );

} /* End of Function packet_read */

static char recvbuf[ 32 * 1024 ];
static char sendbuf[ 32 * 1024 ];
static int  buflen = 0;

static void clearBuf( )
{
    buflen = 0;
}

static int setString( char * s )
{
    int len;

    if( !s )
    {
        s = "";
    }

    len = 1 + strlen( s );

    if( buflen + len >=  32 * 1024 )
    {
        return( -1 );
    }

    memcpy( sendbuf + buflen, s, len );
    buflen += len;

    return( buflen );
}
    
static char *result[ 32 * 1024 ];
int          nresults;

static void parseresult( char * buf, int len )
{
    int i;

    char * start = buf;
    nresults = 0;

    for( i = 0; i < len; i++ )
    {
        if( buf[ i ] == 0 )
        {
            result[ nresults++ ] = start;
            start = buf + i + 1;
        }
    }
}

/**
 * test frame for the ISAM file library
 *
 * This test frame calls the NJS ISAM file library,
 * it is an interactive test frame capable of regression tests.
 *
 * <B>Interactive mode:</B>
 * <UL>
 * Call the program vservtst from a UNIX or DOS shell.
 * <BR>
 * The following commands to test the NJS ISAM File Library are supplied:
 * <UL>
 * <PRE>
 q       FOR QUIT
 open filename keyfile1,dupfile2,... update
 transaction < START | COMMIT | ROLLBACK >
 close
 flush
 insert ,key1,key2... data
 ninsert n key1,key2,... data
 find index key < LT | LE | FI | EQ | LA | GE | GT >
 nfind n index key < LT | LE | FI | EQ | LA | GE | GT >
 get index < NEXT | PREV | FIRST | LAST | THIS >
 datalen
 readdata
 readkey index
 updatedata data
 updatekey index key
 ndelete n
 </PRE>
 * </UL>
 * See \Ref{pblKEYFILE_TestFrame} for an example to interactively use the
 * test frame.
 * </UL>
 * <B>Regression mode:</B>
 * <UL>
 * Five regression test cases are supplied with the NJS ISAM library.
 *
 * ISAM0001.TST, ISAM0002.TST, ISAM0003.TST, ISAM0004.TST and ISAM0005.TST.
 * 
 * ISAM0001.TST and ISAM0004.TST are run when the "make test" 
 * is done. Do the following if you want to run the test cases per hand
 * <PRE>
   1. Build the vservtst executable.          make all
   2. Create the sub directory isamtest.      mkdir isamtest
   3. Clear the sub directory isamtest.       rm imamtest/0*
   4. Run the test frame on this file.        vservtst ISAM0001.TST
   5. Compare ISAM0001.TST and vservtst.log   diff ISAM0001.TST vservtst.log
 </PRE>
 * There should be no differences reported, if so your build of the
 * NJS library is most likely ok!
 *
 * </UL>
 */

static int testFrame( int argc, char * argv[] )
{
    char     command  [ NJS_ISAMTEST_BUFLEN ];
    char     filename [ NJS_ISAMTEST_BUFLEN ];
    char     buffer   [ NJS_ISAMTEST_BUFLEN ];
    int      dowork = 1;
    long     rc = 0;
    int      len;
    int      i = 0;

    char     PID[ 256 ];
    char     CID[ 256 ];
    char     CLID[ 256 ];
    char     SCID[ 256 ];

    char   * addr = NULL;
    int      addrlen = 0;

    struct sockaddr_in saddr;
    char           *sender = ( char * )&saddr;
    int             senderlen;

    struct timeval  timeout;

    int      sockfd = -1;

    strcpy( PID, "0" );
    strcpy( CID, "0" );
    strcpy( CLID, "0" );
    strcpy( SCID, "0" );

    addr = strdup( "0" );

    /*
     * Open a UDP socket
     */
    sockfd = socket( AF_INET, SOCK_DGRAM, 0 );
    if( sockfd < 0 )
    {
        fprintf( stderr, "Failed to open client socket: %s\n",
                 strerror( errno ));
        exit( -1 );
    }

    /*
     * if an argument is given it is treated as a command file
     */
    infile = stdin;
    if( argc > 1 )
    {
        infile = fopen( argv[ 1 ], "r" );
        if( !infile )
        {
            fprintf( stderr, "Failed to open %s, %s\n",
                     argv[ 1 ], strerror( errno ));
            exit( -1 );
        }
    }

    /*
     * open the log file
     */
    logfile = fopen( "./vservtst.log", "wb" );
    if( !logfile )
    {
        fprintf( stderr, "cant open logfile, ./vservtst.log, %s\n",
                 strerror( errno ));
        exit( 1 );
    }

    /*
     * use the default server name and address
     */
    addrlen = srvadr_get( "localhost", 3000, &addr );

    /*
     * main command loop
     */
    while( dowork )
    {
        clearBuf();
        memset( command, 0, sizeof( command ));
        memset( filename, 0, sizeof( filename ));
        memset( buffer, 0, sizeof( buffer ));

        errno = 0;
        senderlen = sizeof( struct sockaddr_in );
        timeout.tv_sec = 1;
        timeout.tv_usec = 0;

        /*
         * read the next command
         */
        printf( "\n##command: \n" );
        getWord( command );

        /*
         * interpret the command given
         */
        if( command[0] == 'q' || command[0] == 'Q' )
        {
            dowork = 0;
        }

        else if( !strcmp( command, "HOST" ))
        {
            int port;

            getWord( filename );
            getWord( buffer );

            port = atoi( buffer );
            if( addr ){ free( addr ); addr = NULL; }

            njssay( "# HOST( %s, %d )\n",
                        filename, port );

            if(( addrlen = srvadr_get( filename, port, &addr )) > 0 )
            {
                njssay( "# ok!\n" );
            }
            else
            {
                njssay( "# not ok! errno %d\n", errno );
            }
        }

        else if( !strcmp( command, "PING" ))
        {
            snprintf( PID, 16, "%08lx", ( long ) (( rand() < 15) ^ rand()));

            setString( "RQ" );
            setString( PID );
            setString( CID );
            setString( "PING" );

            njssay( "# RQ PID CID PING\n" );

            rc = sendto( sockfd, sendbuf, buflen, 0,
                         ( struct sockaddr * )addr, addrlen );
            if( rc < 0 )
            {
                njssay( "# send not ok! errno %d\n", PACKET_ERRNO );
                continue;
            }

            rc = packet_read( sockfd,
                              recvbuf,
                              32 * 1024,
                              sender,
                              &senderlen,
                              &timeout
                            );
            if( rc < 0 )
            {
                njssay( "# receive not ok! errno %d\n", PACKET_ERRNO );
                continue;
            }
            else if( rc == 0 )
            {
                njssay( "# receive not ok! timeout\n" );
                continue;
            }

            parseresult( recvbuf, rc );
            if( nresults < 4 )
            {
                njssay( "# results not ok! n %d", nresults );
                for( i = 0; i < nresults; i++ )
                {
                    njssay( " \"%s\"", result[ i ] );
                }
                njssay( "\n" );
                continue;
            }

            if( strcmp( result[ 0 ], "AN" ))
            {
                njssay( "# header not ok! n %s\n", result[ 0 ] );
                continue;
            }

            if( strcmp( result[ 1 ], PID ))
            {
                njssay( "# PID not ok! n %s != %s\n", PID, result[ 1 ] );
                continue;
            }
            result[ 1 ] = "PID";

            if( strcmp( CID, "0" ))
            {
                if( strcmp( result[ 2 ], CID ))
                {
                    njssay( "# CID not ok! n %s != %s\n", CID, result[ 2 ] );
                    continue;
                }   
            }
            else
            {
                snprintf( CID, 16, "%s", result[ 2 ] );
            }
            result[ 2 ] = "CID";

            if( strcmp( result[ 3 ], "PONG" ))
            {
                njssay( "# tag not ok! n \"%s\"", result[ 3 ] );
                for( i = 4; i < nresults; i++ )
                {
                    njssay( " \"%s\"", result[ i ] );
                }
                njssay( "\n" );
                continue;
            }

            njssay( "# ok!" );
            for( i = 0; i < nresults; i++ )
            {
                njssay( " \"%s\"", result[ i ] );
            }
            njssay( "\n" );
        }

        else if( !strcmp( command, "RQ" ))
        {
            snprintf( PID, 16, "%08lx", ( long ) (( rand() < 15) ^ rand()));

            setString( "RQ" );
            setString( PID );
            setString( CID );

            getWord( buffer );
            len = atoi( buffer );

            for( i = 0; i < len; i++ )
            {
                getWord( buffer );

                if( !strcmp( "scid", buffer ))
                {
                    setString( SCID );
                }
                else if( !strcmp( "clid", buffer ))
                {
                    setString( CLID );
                }
                else
                {
                   setString( buffer );
                }
            }

            parseresult( sendbuf, buflen );

            njssay( "# RQ PID CID\n" );
            for( i = 0; i < nresults; i++ )
            {
                if( !strcmp( SCID, result[ i ] ))
                {
                     njssay( " scid" );
                }
                else if( !strcmp( CLID, result[ i ] ))
                {
                     njssay( " clid" );
                }
                else
                {
                    njssay( " \"%s\"", result[ i ] );
                }
            }
            njssay( "\n" );

            rc = sendto( sockfd, sendbuf, buflen, 0,
                         ( struct sockaddr * )addr, addrlen );
            if( rc < 0 )
            {
                njssay( "# send not ok! errno %d\n", PACKET_ERRNO );
                continue;
            }

            rc = packet_read( sockfd,
                              recvbuf,
                              32 * 1024,
                              sender,
                              &senderlen,
                              &timeout
                            );
            if( rc < 0 )
            {
                njssay( "# receive not ok! errno %d\n", PACKET_ERRNO );
                continue;
            }
            else if( rc == 0 )
            {
                njssay( "# receive not ok! timeout\n" );
                continue;
            }

            parseresult( recvbuf, rc );
            if( nresults < 4 )
            {
                njssay( "# results not ok! n %d", nresults );
                for( i = 0; i < nresults; i++ )
                {
                    njssay( " \"%s\"", result[ i ] );
                }
                njssay( "\n" );
                continue;
            }

            if( strcmp( result[ 0 ], "AN" ))
            {
                njssay( "# header not ok! n %s\n", result[ 0 ] );
                continue;
            }

            if( strcmp( result[ 1 ], PID ))
            {
                njssay( "# PID not ok! n %s != %s\n", PID, result[ 1 ] );
                continue;
            }
            result[ 1 ] = "PID";

            if( strcmp( CID, "0" ))
            {
                if( strcmp( result[ 2 ], CID ))
                {
                    njssay( "# CID not ok! n %s != %s\n", CID, result[ 2 ] );
                    continue;
                }   
            }
            else
            {
                snprintf( CID, 16, "%s", result[ 2 ] );
            }
            result[ 2 ] = "CID";

            njssay( "# ok!" );
            for( i = 0; i < nresults; i++ )
            {
                if( !strcmp( result[ i ], "SCID" ) && i < nresults - 1 )
                {
                    snprintf( SCID, 16, "%s", result[ ++i ] );
                    result[ i ] = "scid";
                }
                else if( !strcmp( result[ i ], "CLID" ) && i < nresults - 1 )
                {
                    snprintf( CLID, 16, "%s", result[ ++i ] );
                    result[ i ] = "clid";
                }
                else
                {
                    njssay( " \"%s\"", result[ i ] );
                }
            }
            njssay( "\n" );
        }

        else
        {
            printf( "# q       FOR QUIT\n" );
            printf( "# HOST hostname port\n" );
            printf( "# PING\n" );
            printf( "# RQ <n> ARG1 ARG2 ... ARGN\n" );
        }
    }

    return( 0 );
}

int main( int argc, char * argv[] )
{
    return( testFrame( argc, argv ));
}

static void njssay(
char *p1,  char *p2,  char *p3,  char *p4,  char *p5,
char *p6,  char *p7,  char *p8,  char *p9,  char *p10,
char *p11, char *p12, char *p13, char *p14, char *p15
)
{
    fprintf( stdout, p1, p2, p3, p4, p5, p6, p7, p8,
             p9, p10, p11, p12, p13, p14, p15 );
    fprintf( logfile, p1, p2, p3, p4, p5, p6, p7, p8,
             p9, p10, p11, p12, p13, p14, p15 );
    //fflush( logfile );
    //fflush( stdout );
}

