/*
 * test program to stress DECnet script connections.
 *
 * Arguments:
 *    script_name
 */
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "decnet_access.h"
/*
 * Global variables.
 */
int http_log_level = 0;
static struct {
    int debug;
    int thread_count;		/* Number of worker threads */
    int duration;		/* length of time to run test */
    int delay;
    char *taskspec;		/* DECnet task spec. for scriptserver */
    char *bindir;		/* Script directory */
    char *subfunc;		/* sub-funcion */
    char *method;
    char *protocol;
    char *url;
    char *script_name;
    char *path;			/* script prefix */
    char *arg;
} param;

static struct {
    pthread_mutex_t lock;
    int active;			/* set to 0 to stop attempts */
    int connect_attempts;
    int connect_success;
    int connect_fail;
    int disconnect;
    int read_count;
    int byte_count;
} counter;
struct worker_context {
    pthread_t tid;
    int id;
};

static struct {
    char *tag;
    char *response;
    int action;
} command[] = {
    { "<DNETPATH>", "", 2 }, { "<DNETTEXT>", "</DNETTEXT>", 1 },
    { "<DNETCGI>", "</DNETCGI>", 1 }, { "<DNETRAW>", "</DNETRAW>", 1 },
    { "<DNETREUSE>", "", 7 }, { "<DNETINVCACHE>", "", 3 },
    { "<DNETBINDIR>", "", 2 }, { "<DNETURL>", "", 2 },
    { "<DNETARG>", "", 2 }, { "<DNETARG2>", "", 2 },
    { "<DNETID>", "OSU/t1 127.0.0.1 80 1111 127.0.0.1 ", 2 }, 
    { "<DNETID2>", "OSU/t1 127.0.0.1 80 1111 127.0.0.1  localhost", 2 },
    { "<DNETXLATE>", "", 4 }, { "<DNETXLATEV>", "", 4 },
    { "<DNETRECMODE>", "", 6 }, { "<DNETHDR>", "", 5 },
    { (char *) 0, "", 0 }	/* end of list */
};
/****************************************************************************/

static int get_parameters ( int argc, char **argv )
{
    int i, slash_count;
    char *url;
    /*
     * Initialize parameter block.
     */
    param.debug = 0;
    param.thread_count = 10;
    param.duration = 60;			/* seconds */
    param.delay = 0;				/* milliseconds */
    param.taskspec = "0::\"0=WWWEXEC\"";
    param.bindir = "www_root:[bin]";
    param.subfunc = "HTBIN";
    param.method = "GET";
    param.protocol = "HTTP/1.0";
    param.url = (char *) 0;
    param.script_name = (char *) 0;
    param.path = (char *) 0;
    param.arg = "";
    /*
     * Override parameters with command line options.
     */
    for ( i = 1; i < argc; i++ ) if ( argv[i][0] == '-' ) {
	if ( 0 == strcmp ( argv[i], "-threads" ) ) {
	    i++;
	    if ( i >= argc ) return 0;
	    param.thread_count = atoi ( argv[i] );
	} else if ( 0 == strcmp ( argv[i], "-duration" ) ) {
	    i++;
	    if ( i >= argc ) return 0;
	    param.duration = atoi ( argv[i] );
	} else if ( 0 == strcmp ( argv[i], "-taskspec" ) ) {
	    i++; if ( i >= argc ) return 0;
	    param.taskspec = argv[i];
	} else if ( 0 == strcmp ( argv[i], "-bindir" ) ) {
	    i++; if ( i >= argc ) return 0;
	    param.bindir = argv[i];
	} else if ( 0 == strcmp ( argv[i], "-method" ) ) {
	    i++; if ( i >= argc ) return 0;
	    param.bindir = argv[i];
	} else if ( 0 == strcmp ( argv[i], "-protocol" ) ) {
	    i++; if ( i >= argc ) return 0;
	    param.bindir = argv[i];
	} else if ( 0 == strcmp ( argv[i], "-debug" ) ) {
	    param.debug = 1;
	} else {
	    fprintf ( stdout, "Unknown option: '%s'\n", argv[i] );
	    return 0;
	}
    } else {
	/*
	 * Argument is URL path to execute.
	 */
	param.url = argv[i];
    }
    /*
     * parse URL string.
     */
    if ( !param.url ) return 0;
    url = malloc ( strlen ( param.url ) + 1 );
    strcpy ( url, param.url );
    for ( i = slash_count = 0; url[i]; i++ ) if ( url[i] == '/' ) {
	slash_count++;
	if ( slash_count == 2 ) {
	    url[i] = '\0';
	    param.path = malloc ( i + 1 );
	    sprintf ( param.path, "%s/", url );
	    url[i] = '/';
	}
    } else if ( url[i] == '?' ) {
	param.arg = malloc ( strlen ( &url[i] ) + 1 );
	strcpy ( param.arg, &url[i] );
	break;
    }
    command[0].response = param.path;		/* <DNETPATH> */
    command[6].response = param.bindir;		/* <DNETBINDIR> */
    command[7].response = url;			/* DNETURL */
    command[8].response = param.arg;
    command[9].response = param.arg;
    return 1;
}

/****************************************************************************/
/* Lock counter mutex and test active flag.  If inactive, unlock and return 0,
 * otherwise keep mutex locked and return 1.
 */
static int lock_counter_if()
{
    pthread_mutex_lock ( &counter.lock );
    if ( !counter.active ) {
	pthread_mutex_lock ( &counter.lock );
	return 0;
    }
    return 1;
}
/****************************************************************************/
static void *worker_main ( void *context_null )
{
    struct worker_context *ctx;
    int i, status, transferred, j, recmode, bytes, reads, reuse_active;
    void *cnx;
    char errmsg[256];
    char buffer[4097];

    ctx = (struct worker_context *) context_null;
    /*
     * Main loop.
     */
    reuse_active = 0;
    for ( i = 0; i >= 0; i++ ) {
	/*
	 * Connect to scriptserver.
	 */
	if ( !lock_counter_if() ) break;
	counter.connect_attempts++;
	pthread_mutex_unlock ( &counter.lock );
	if ( reuse_active ) status = 1;
	else status = dnet_connect ( param.taskspec, &cnx );
	if ( !lock_counter_if() ) break;
	if ( status&1 ) counter.connect_success++; else counter.connect_fail++;
	pthread_mutex_unlock ( &counter.lock );
	if ( (status&1) == 0 ) {
	    /*
	     * Connect failed.
	     */
	    struct timespec interval;
	    dnet_format_error ( status, errmsg, sizeof(errmsg)-1 );
	    fprintf(stderr, "Error %d connecting to '%s': %s\n", status,
		param.taskspec, errmsg );
	    interval.tv_sec = 2;
	    interval.tv_nsec = 0;
	    pthread_delay_np ( &interval );
	    continue;
	}
	/*
	 * Send prologue.
	 */
	if ( (status&1) == 1 ) status = dnet_write ( cnx, 
		param.subfunc, strlen(param.subfunc), &transferred );
	if ( (status&1) == 1 ) status = dnet_write ( cnx,
		param.method, strlen(param.method), &transferred );
	if ( (status&1) == 1 ) status = dnet_write ( cnx,
		param.protocol, strlen(param.protocol), &transferred );
	for ( j = 0; param.url[j]; j++ ) if ( param.url[j] == '?' ) break;
	if ( (status&1) == 1 ) status = dnet_write ( cnx,
		param.url, j, &transferred );
	/*
	 * do dialog.
	 */
	bytes = 0;
	reads = 0;
	recmode = 0;
	while ( status&1 ) {
	    int num;
	    /*
	     * Read tag from scriptserver and lookup in command table.
	     */
	    status = dnet_read ( cnx, buffer, 1024, &transferred );
	    if ( (status&1) == 0 ) break;
	    buffer[transferred] = '\0';
	    for ( num = 0; command[num].action; num++ ) {
		if ( strcmp ( command[num].tag, buffer ) == 0 ) break;
	    }
	    if ( command[num].action == 0 ) {
		fprintf ( stdout,"%d: protocol error: '%s'\n", ctx->id, buffer );
		break;	/* unknown */
	    }
	    if ( param.debug ) fprintf(stdout,
		"%d: tag: '%s', action: %d, rsp: '%s'\n", ctx->id,
		   buffer, command[num].action, command[num].response );
	    /*
	     * Take action.
	     */
	    if ( command[num].action == 1 ) {
		/* Terminal, read lines until response seen. */
		int len;
		len = strlen ( command[num].response );
		if ( strcmp ( command[num].tag, "<DNETTEXT>" ) == 0 )
			recmode = 1;
		while ( status&1 ) {
		    status = dnet_read ( cnx, buffer, sizeof(buffer)-1,
			&transferred );
		    if ( (status&1) == 0 ) break;
		    if ( (i==0) && (param.thread_count == 1) ) {
			buffer[transferred] = '\0';
			fprintf ( stdout, "%s%s", buffer, recmode ? "\n" : "" );
		    }
		    if ( transferred == len ) {
		       if ( strncmp ( buffer, command[num].response, len ) == 0 )
			break;
		    }
		    bytes += transferred;
		    reads++;
		}
		if ( param.debug ) fprintf(stdout, "%d: data bytes: %d\n",
			ctx->id, bytes );
		break;
	    } else if ( command[num].action == 2 ) {
		/* Simple response */
		status = dnet_write ( cnx, command[num].response,
			strlen(command[num].response), &transferred );
	    } else if ( command[num].action == 4 ) {	/* translate */
		status = dnet_read ( cnx, buffer, sizeof(buffer),
			&transferred );
		if ( (status&1) == 0 ) break;
		status = dnet_write ( cnx, command[num].response,
			strlen(command[num].response), &transferred );
	    } else if ( command[num].action == 5 ) {
		/* Send dummy header lines */
		status = dnet_write ( cnx, "accept: */*", 11, &transferred );
		if ( (status&1) == 0 ) break;
		status = dnet_write ( cnx, "", 0, &transferred );
	    } else if ( command[num].action == 6 ) {
		recmode = 1;
	    } else if ( command[num].action == 7 ) {
		reuse_active = 1;
	    }
	}
	if ( (status&1) == 0 ) {
	    dnet_format_error ( status, errmsg, sizeof(errmsg)-1 );
	    fprintf(stderr, "Abnormal script termination, code %d: %s\n", status,
		 errmsg );
	}
	/*
	 * Send <DNETREUSE> to confirm.
	 */
	if ( reuse_active ) {
	    status = dnet_write ( cnx, "<DNETREUSE>", 11, &transferred );
	    if ( (status&1) == 1 ) status = dnet_write ( 
		cnx, "<DNETREUSE>", 11, &transferred );
	    if ( !lock_counter_if() ) break;
	    counter.disconnect++;
	    counter.read_count += reads;
	    counter.byte_count += bytes;
	    pthread_mutex_unlock ( &counter.lock );
	    /*
	     * Skip disconnect.
	     */
	    if ( (status&1) == 1 ) continue;
	}
	/*
	 * Close connection.
	 */
	reuse_active = 0;
	dnet_disconnect ( cnx );
	if ( !lock_counter_if() ) break;
	counter.disconnect++;
	counter.read_count += reads;
	counter.byte_count += bytes;
	pthread_mutex_unlock ( &counter.lock );
    }
    return (void *) 0;
}
/****************************************************************************/
int main ( int argc, char **argv )
{
    pthread_cond_t timeout;
    int i;
    struct timespec delta, expiration;
    struct worker_context *context;
    /*
     * Extract operating parameters from command line.
     */
    if ( get_parameters ( argc, argv ) == 0 ) {
	fprintf(stderr, "Usage: stress [-threads n] [-duration l] path\n" );
	return 1;
    }
    /*
     * Initialize decnet_access and counter block.
     */
    pthread_cond_init ( &timeout, NULL );
    dnet_initialize ( );
    pthread_mutex_init ( &counter.lock, NULL );
    counter.active = 1;
    counter.connect_attempts = 0;
    counter.connect_success = 0;
    counter.connect_fail = 0;
    counter.disconnect = 0;
    counter.read_count = 0;
    counter.byte_count = 0;
    /*
     * Create threads.  hold mutex to block activity until ready.
     */
    pthread_mutex_lock ( &counter.lock );
    context = (struct worker_context *) malloc ( param.thread_count * 
	sizeof(struct worker_context) );
    for ( i = 0; i < param.thread_count; i++ ) {
	int status;
	context[i].id = i + 1;
	status = pthread_create ( &context[i].tid, NULL, worker_main,
		(void *) &context[i] );
    }
    /*
     * Set timer and wait for duration.
     */
    delta.tv_sec = param.duration;
    delta.tv_nsec = 0;
    if ( 0 == pthread_get_expiration_np ( &delta, &expiration ) ) {
	int status;
	fprintf ( stdout, "Waiting for %d seconds...\n", param.duration );
	do {
	    status = pthread_cond_timedwait ( &timeout, &counter.lock,
			&expiration );
	} while ( status == 0 );
    } else {
	fprintf(stderr, "Error computing expriation time\n" );
    }
    /*
     * Show results.
     */
    counter.active = 0;
    pthread_mutex_unlock ( &counter.lock );
    printf ( "Connect attempts: %d, success: %d, fail: %d, discon: %d\n",
	counter.connect_attempts, counter.connect_success, 
	counter.connect_fail, counter.disconnect);
    printf ( "Total of %d data bytes in %d reads\n", counter.byte_count,
	counter.read_count );
    return 1;
}
