/* Written by Germano Caronni and Werner Almesberger */
/* (c) by G. Caronni in '94 */
/* This program is under the GNU Public License Version 2 */

#include <stdio.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <malloc.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
/*#include <getopt.h> i have it in stdlib.h*/
#include <signal.h>
#ifndef USE_PIPE
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#endif
#include <sys/utsname.h>
#include <pwd.h>
#ifdef _AIX
#include <sys/select.h>
#endif
#ifdef SOCKS
#include <socks.h>
#endif

#include "dic.h"
#include "keyclnt.h"

#include "pipe.h"

static char buf[MAX_MSG];
static int debug = 0;
static int byebye = 0;

extern char *optarg;
extern int optind;

double measure(unsigned long, int);
static void integrity(int);
static void send_rep(char *);
static unsigned long iterations (int , int);
static void usage(const char *prog);
static void stopme(int dummy);
static void send_g(int iter, int time_work);

double measure(unsigned long iter, int iflag)
{
    struct timeval stop;
    struct timezone dummy;
    double len;

    fprintf(iflag ? stderr : stdout, "Performance test: %ld iterations in ", iter);
    fflush(iflag ? stderr : stdout);
    if (gettimeofday(&stop,&dummy) < 0) PDIE("gettimeofday");
    len=stop.tv_sec*1000000.0+stop.tv_usec;
    init("112233445566",iter, "1234567812345678", "1234567812345678");
    doit();
    if (gettimeofday(&stop,&dummy) < 0) PDIE("gettimeofday");
    len= (((double)(stop.tv_sec*1000000.0+stop.tv_usec)) - len)/1000000.0;
    fprintf(iflag ? stderr : stdout, "%3.2f seconds.\n",len);
    return len;
}

static void integrity(int iflag)
{
     fprintf(iflag ? stderr : stdout, "Integrity test ");
     fflush(iflag ? stderr : stdout);
     /*init("282604780651",iter,"77E459E9C593F4F0","BF8886D2A950841C");*/
     init("272604780000",1000000,"77E459E9C593F4F0","BF8886D2A950841C");
     if (doit()==1 && strcmp("282604780651",getresult())==0) {
	  fprintf(iflag ? stderr : stdout, "okay. \n");
     } else {
	  fprintf(iflag ? stderr : stdout, " FAILED !\n");
	  exit(-1);
     }
}


static void send_rep(char *m)
{
    fd_set fdset;
    struct timeval to;

    struct sockaddr_in from;
    int len,fromlen,fds,msglen;

    unsigned long retry_delay=RETRY_DELAY;
    unsigned long timeout;

    while (1) {
	FD_ZERO(&fdset);
	FD_SET(RECV,&fdset);
	to.tv_sec = 0;
	to.tv_usec = 0;
	if ((fds = select(RECV+1,&fdset,NULL,NULL,&to)) < 0) {
	    if (errno!=EINTR) PDIE("select")
	    else continue;
        }
	if (!fds) break;
	fromlen = sizeof(from);
	(void) RECVFROM(RECV,buf,MAX_MSG,0,(struct sockaddr *)&from,&fromlen);
    }
    len = strlen(m);

    timeout=time((time_t *)0)+MAX_DELAY*60;

    while(time((time_t *)0)<timeout) {
	if (debug) printf("Send: %s\n",m);
	if (SENDTO(SEND,m,len,0,(struct sockaddr *)&addr,sizeof(addr)) < 0) 
	    perror("sendto");
	FD_ZERO(&fdset);
	FD_SET(RECV,&fdset);
	to.tv_sec = retry_delay>MAX_RETRY_DELAY?
			MAX_RETRY_DELAY:
			(retry_delay=retry_delay+RETRY_DELAY);
	to.tv_usec = 0;
	if ((fds = select(RECV+1,&fdset,NULL,NULL,&to)) < 0) {
	    if (errno!=EINTR) PDIE("select")
	    else continue;
        }
	if (!fds) continue;

	fromlen = sizeof(from);
	if ((msglen = RECVFROM(RECV,buf,MAX_MSG,0,(struct sockaddr *)&from,
							&fromlen)) < 0) {
	    if (errno == ECONNREFUSED) {
                /* this is a very stange Linux 2.0.27 'feature' */
		continue;
	    } else PDIE("recvfrom");
        }

	if (from.sin_addr.s_addr != addr.sin_addr.s_addr) {
	    fprintf(stderr, "Received packet from host %lX\n",
		   (unsigned long)from.sin_addr.s_addr);
	    continue;
	}

	buf[msglen] = 0;
	if (debug) printf("Rcvd: %s\n",buf);
	return;
    }
    fprintf(stderr, "\nARGLLLLLLLLLLLLGNAAAAAAAAAA!!!!!\n");
    exit(2);
}

static unsigned long iterations (int time_mode, int iflag)
{
     double len;
     unsigned long iter;

     fprintf(iflag ? stderr : stdout,
	     "Job length scheduled to %d minutes.\n",time_mode/60);
     fprintf(iflag ? stderr : stdout,
	     "Timeout occurs after %d minutes.\n",time_mode/30);
     
     len = measure(100000, iflag);

     iter=1.0e5/len*(double)time_mode;
     if (iter> 0x7fffffff) iter = 0x7fffffff;
     fprintf(iflag ? stderr : stdout, 
	    "Requesting %ld keys.\n",iter);

     return iter;
}

static void usage(const char *prog)
{
     fprintf(stderr, 
     "usage: %s -m\n", prog);
     fprintf(stderr, 
     "       %s [-d] [-t job-length] [-l] [-L lockfile] [-K killfile]\n"
     "              [-p port] [-f] [-e] [-i] [-n nicety] server [ procs ]\n", 
     prog);
     printf("For those without a manpage: kom30.ethz.ch is the host.\n"
	    "ftp://ftp.tik.ee.ethz.ch/pub/projects/dic/\n\n");
}

static int exists(const char *fname)
{
     struct stat sbuf;
     return stat(fname, &sbuf) == 0;
}

static void stopme(int dummy)
{
     signal(SIGUSR1, stopme);
     byebye = 1;
}

static void send_g(int iter, int time_mode)
{
    char lbuf[20], msg[MAX_MSG];
    struct utsname u;
    struct passwd *who;

    uname(&u);
    if (!(who=getpwuid(getuid())))
   	 sprintf(lbuf,"%lud",(unsigned long) getuid());

    sprintf(msg,"G%d %d <%s,%s-%s-%s,%s,%s,%d>", iter, time_mode*2,
	    VERSION, u.sysname, u.release, u.machine, u.nodename, 
	    who?who->pw_name:lbuf, (int)getpid());
    send_rep(msg);

}

void main(int argc, char *argv[])
{
    unsigned long id;
    unsigned long range;
    int number = 0;
    pid_t pid = 0;
    unsigned long iter=1000000, old_iter;

    struct timeval tstart,tstop;
    struct timezone dummy;
    int time_mode=SECONDS;
    
    double factor;

    struct hostent *temp;
    char msg[MAX_MSG],clear[20],start[20],cipher[20];
    const char *prog=argv[0];
    const char *killfile = CLIENT_KILL;
    const char *lockfile = LOCK;
    int lockfd;
    int x;
    int eflag=0, iflag=0, lflag=0;
    int nicety = NICE_VAL;
    int forkalways=0;
    int ischild = 0;

    struct in_addr our_ip;
    int our_family;
    short dic_port;

#ifdef SOCKS
    
    SOCKSinit(argv[0]);

#endif /* SOCKS */

    signal(SIGUSR1, stopme);
    signal(SIGCHLD, SIG_IGN);


    if (argc == 2 && !strcmp(argv[1],"-m")) {
        printf("DIC Client v%s\n",VERSION);
	measure(1000000, 0);
        integrity(0);
	exit(0);
    }

    our_ip.s_addr= INADDR_ANY;
    our_family = AF_INET;
    dic_port = DIC_PORT;

    while((x = getopt(argc, argv, "I:dt:eilL:K:fn:p:")) != EOF) {
	 switch(x) {
	 case 'I':
	      if((temp = gethostbyname(optarg))) {
		   memcpy(&our_ip, temp->h_addr, temp->h_length);
		   our_family=temp->h_addrtype;
	      } else {
		   our_family=AF_INET;
		   if((our_ip.s_addr = inet_addr(optarg)) == -1)
			PDIE(optarg);
	      }
	      our_ip.s_addr = ntohl(our_ip.s_addr);
	      break;
	 case 'd':
	      debug = 1;
	      break;
	 case 't':
	      time_mode = atoi(optarg) * 60;
	      break;
	 case 'e':
	      eflag = 1;
	      break;
	 case 'i':
	      iflag = 1;
	      break;
	 case 'L':
	      lockfile = optarg;
	      break;
	 case 'K':
	      killfile = optarg;
	      break;
	 case 'l':
	      lflag = 1;
	      break;
	 case 'n':
	      nicety = atoi(optarg);
	      break;
	 case 'f':
	      forkalways=1;
	      break;
	 case 'p':
	      dic_port = atoi(optarg);
	      break;
	 default: 
	      usage(prog);
	      exit(1);
	 }
    }

    if(!eflag) iflag = 0;

    argv += optind-1;
    argc -= optind-1;

    if (argc != 2 && argc != 3) {
	 usage(prog);
	 exit(0);
    }

    if(argc == 3) 
	 number = atoi(argv[2]);
    
    if(number == 0) number = 1;
    
    if (exists(killfile)) {
        fprintf(iflag ? stderr : stdout,
              "DIC Client v%s: Startup prohibited by %s\n",
              VERSION, killfile);
        exit(1);
    }


    fprintf(iflag ? stderr : stdout, "DIC Client v%s\n",VERSION);

    integrity(iflag);

    iter = iterations(time_mode, iflag);


    if(eflag || number > 1) {
	 int devnullfd;
	 int i;

	 for(i = 1 - (eflag ? 1 : 0); i < number; i++)
	      if((pid = fork()) < 0) {
		   PDIE("fork");
	      } else if (pid) {
		   if(iflag) printf("%d\n", (int) pid);
	      } else {
		   ischild = 1;
		   number = i;
		   break;
	      }
	 
	 if(pid && eflag) exit(0);
	 
	 /* ---  Client starts here --- */
	 
	 /* Essentially, this is daemon(3) from BSD libcs. */
	 
	 if(eflag) {
	      
	      setsid();
	      if((devnullfd = open("/dev/null", O_RDWR,0)) != -1) {
		   dup2(devnullfd, 0);
		   dup2(devnullfd, 1);
		   dup2(devnullfd, 2);
		   if(devnullfd > 2)
			close(devnullfd);
	      }
	      
	      lflag = 1;
	 }
    }
    
    if(lflag) {
	 
	 char buff[1024];
	 
	 if(ischild) {
	      sprintf(buff, "%s-%d", lockfile, number);
	      lockfd=open(buff, O_RDWR | O_CREAT, 0644);
	 } else {
	      lockfd = open(lockfile, O_RDWR | O_CREAT, 0644);
	 }
	 
	 if (lockfd<0) exit(1);
	 
#ifdef USE_LOCKF
	 if (lockf(lockfd, F_TLOCK, 0) < 0) exit(1);
#else
	 if (flock(lockfd, LOCK_EX|LOCK_NB) < 0) exit(1);
#endif
    }

    nice(nicety);

    if ((SEND = socket(PF_INET,SOCK_DGRAM,0)) < 0) PDIE("socket");
    memset(&addr,0,sizeof (addr));
    addr.sin_family = our_family;
    addr.sin_port = htons(0);
    addr.sin_addr.s_addr = htonl(our_ip.s_addr);
    if (bind(SEND,(struct sockaddr *) &addr,sizeof(addr)) < 0) PDIE("bind");
    addr.sin_port = htons(dic_port);
    if ((temp = gethostbyname(argv[1]))) {
	addr.sin_family = temp->h_addrtype;
	memcpy(&addr.sin_addr,temp->h_addr,temp->h_length);
    }
    else {
	addr.sin_family = AF_INET;
	if ((addr.sin_addr.s_addr = inet_addr(argv[1])) == -1) PDIE(argv[1]);
    }


    send_g(iter,time_mode);

    while (1) {
        again:
	if (buf[0]=='K') {
	    printf("Client was killed (%s)\n",buf);
	    exit(2);
	} else if (buf[0]=='R') {
	    if (!iter) iter=10000;
	    send_g(iter,time_mode);
	    goto again;
	} else if (buf[0]=='I') {
	    /* server is giving us an idle packet, this happens when our
	     * new range was set to 0. 
	     * This happens a) when we want it because of a pending kill,
	     * where we just wanted to collect a confirmation, and b) when
	     * our iter calculation produced junk.
	     */
            if (byebye) {
		printf("Got return receipt from server -- terminating.\n");
		exit(0);
            }
            /* what now ? send a H packet which is nearly a G packet */
	    if (!iter) iter=10000;
	    sprintf(msg,"H%ld %d",iter,time_mode*2);
	    send_rep(msg);
	    goto again;
	}

	if (sscanf(buf,"J%lu %s %s %s %lu",&id,clear,cipher,start,&range)!= 5) {
	    printf("Invalid msg: '%s'\n",buf);
	    exit(1);
	}


	printf("[%d:%ld] ",number, range);
	fflush(stdout);

	init(start,range,clear,cipher);

        if (gettimeofday(&tstart,&dummy) < 0) PDIE("gettimeofday");

	x = doit();
	if (x==1) {
	    printf("\nFound %s\n", getresult());
	    sprintf(msg,"F%s %s %s",clear,cipher,getresult());
	    send_rep(msg);
	    goto again;
	}
	fflush(stdout);

        if (gettimeofday(&tstop,&dummy) < 0) PDIE("gettimeofday");
        
	if (tstop.tv_usec < tstart.tv_usec) {
	    tstop.tv_sec--;
	    tstop.tv_usec+=1000000;
	}

	old_iter=range;

/* Problem: If the server once gives back a shorter range that we required,
   ranges will slowly adapt back to our normal value, because we request a
   shorter range next time that we really could handle. Adaptivity should be
   based on actual computation power per time base, not per assumed base
   gec 3.2.96
   lets try this to compensate... */

        factor= ((double)iter)/((double)range);
        /*printf("Factor is %2.2f\n",factor);  -- unused for now*/

	iter = (4.0 * ((double) range) + (
	     ((double) range) /
	     (((((double)tstop.tv_sec)-((double)tstart.tv_sec))*1000000.0)+
	       ((double)tstop.tv_usec)-((double)tstart.tv_usec)) * 
	       ((double)(debug?5:(time_mode?time_mode:SECONDS)) * 1000000.0)
	     ) ) / 5.0  ;

        if (iter >= 0x7fffffff)  /* float overflow ? */
	    iter = old_iter;
	else if ((iter > 10*old_iter) && (10*old_iter > 0)) 
	    iter = 10 * old_iter;

        if (!byebye) byebye = exists(killfile);
	if (byebye) {
	    printf("Batch done in %3.2f minutes. Sending last message...\n",
		   ((double)tstop.tv_sec-tstart.tv_sec)/60.0);
	    sprintf(msg,"D%s %d %d",buf,0,time_mode*2); 
	} else {
	    printf("Batch done in %3.2f minutes. " 
		   "Requesting new job with size %ld\n", 
		   ((double)tstop.tv_sec-tstart.tv_sec)/60.0,iter);
	    sprintf(msg,"D%s %ld %d",buf,iter,time_mode*2); 
	}
	send_rep(msg);

	if(forkalways) if(fork()) exit(0);
    }
}

