/* 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>
#include <stdarg.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/utsname.h>

#include <pwd.h>
#if defined _AIX || defined __EMX__
#include <sys/select.h>
#endif
#ifdef SOCKS
#include <socks.h>
#endif


#ifdef HAVE_SYSLOG
#include <syslog.h>
#endif

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

#include "pipe.h"

static char buf[MAX_MSG];
static int debug = 0;
static int byebye = 0;
static int unlockonusr1 = 0;
static int use_syslog = 0;
static const char *lockfile = LOCK;
static int lockfd;

static FILE *fstd;

extern char *optarg;
extern int optind;

static double measure(double iter, int verbose);
static void integrity(int verbose);
static void send_rep(char *m);
static double bloody_floor (double in);
static double iterations (int time_mode);
static void usage(const char *prog);
static void stopme(int dummy);
static void send_g(double iter, int time_work);
static void userinfo(char *format, ...);

#ifndef DEBUG_FORMATS
static void userinfo (char *format, ...)
{
     va_list param;
     
     va_start (param, format);  
#ifdef HAVE_SYSLOG
     if(use_syslog) {
          char out[2048]; /* XXX Avoid buffer overflows */
	  vsprintf (out, format, param);
	  syslog (LOG_NOTICE, "%s", out);
     } else
#else
	  vprintf (format, param);
#endif
     va_end (param);
}
#else
#define userinfo printf
#endif

static double bloody_floor(double in)
{
    unsigned long int hi, lo;
    hi = in/4294967296.0; lo = in-4294967296.0*hi;
    return ((double) hi)*4294967296.0+((double) lo);
}

static double measure(double iter, int verbose)
{
    struct timeval stop;
    struct timezone dummy;
    double len;

    if (verbose) fprintf(fstd, "Performance test: %.0f iterations in ", iter);
    if (verbose) fflush(fstd);
    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;
    if (verbose) fprintf(fstd, "%3.2f seconds.\n",len);
    return len;
}

static void integrity(int verbose)
{
     if (verbose) fprintf(fstd, "Integrity test ");
     if (verbose) fflush(fstd);
     /*init("282604780651",iter,"77E459E9C593F4F0","BF8886D2A950841C");*/
     init("272604780000",1000000.0,"77E459E9C593F4F0","BF8886D2A950841C");
     if (doit()==1 && strcmp("282604780651",getresult())==0) {
	  if (verbose) fprintf(fstd, "okay. \n");
     } else {
	  fprintf(stderr, "\r*** INTEGRITY TEST 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;

	for (;;) {
	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");
	if (SENDTO(SEND,m,len,0,(struct sockaddr *)&addr,sizeof(addr)) < 0) 
	    perror("sendto");
	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 double iterations (int time_mode)
{
     double len;
     double iter;

     fprintf(fstd, "Job length scheduled to %d minutes.\n",time_mode/60);
     fprintf(fstd, "Timeout occurs after %d minutes.\n",time_mode*3/60);
     
     len = measure(200000.0, 0);

     iter=2.0e5*(double)time_mode/len;
     iter=bloody_floor(iter);
     fprintf(fstd, "Requesting %.0f keys.\n",iter);

     return iter;
}

static void usage(const char *prog)
{
     fprintf(stderr, "usage: %s -m OR\n", prog);
     fprintf(stderr, 
"       %s [-d] [-c count] [-t job-length] [-T run-length] [-l] [-L lockfile]\n"
"              [-K killfile] [-S] [-u] [-p port] [-f] [-e] [-i] [-n nicety]\n"
"              server [ procs ]\n", prog);

    fprintf(stderr,"Where:\n"
"-m                 : causes the client to do measurements and then terminate\n"
"-d                 : enabled debugging (5 seconds per batch, more output\n"
"-c count           : causes the client to terminate after count batches\n"
"-t job-length      : Amount of minutes a batch is supposed to take\n"
"-T task length     : Amount of minutes after which the client dies\n"
"-l (ell)           : enable locking\n"
"-L lockfile        : specify lock file location\n"
"-K killfile        : if the file given is found, the client terminates\n"
"-I address         : is undocumented\n"
"-p port            : should not be used\n"
"-f                 : client forks after each batch of keys\n"
"-e                 : the client detaches and does locking\n"
"-i                 : print PID on stdout, rest should go to stderr\n"
"-n nicety          : Value of nice-ness the process has to assume\n"
#ifdef HAVE_SYSLOG
"-S                 : The client will log some progress noise using syslog\n"
#else
"-S                 : Progress noise to Syslog (disabled)\n"
#endif
"-u                 : The client will remove the lock file when receiving\n"
"                     a SIGUSR1 signal.\n"
"server             : kom30.ethz.ch\n"
"procs              : The number of processes to create\n"
    );
     fprintf(stderr,"For those without a manpage: kom30.ethz.ch is the host.\n"
	            "ftp://ftp.tik.ee.ethz.ch/pub/projects/dic/\n");
}

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

static void stopme(int dummy)
{
     signal(SIGUSR1, stopme);

     if(unlockonusr1) {
	  unlink(lockfile);
	  close(lockfd);
	  unlockonusr1 = 0;
     }
     byebye = 1;
}

static void send_g(double 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%.0f %d <%s,%s-%s-%s,%s,%s,%u>", iter, time_mode*3,
	    VERSION, u.sysname, u.release, u.machine, u.nodename, 
	    who?who->pw_name:lbuf, (unsigned int)getpid());
    send_rep(msg);

}

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

    struct timeval tstart,tstop;
    struct timezone dummy;
    int time_mode=SECONDS;
    time_t expected;
    time_t dying_time = 0;
    

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

    struct in_addr our_ip;
    int our_family;
    short dic_port;

    fstd=stdout;

#ifdef SOCKS
    
    SOCKSinit(argv[0]);
    printf("SOCKS ");

#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, 1);
        integrity(1);
	exit(0);
    }

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

    while((x = getopt(argc, argv, "T:c:I:dt:eilL:K:fn:p:uS")) != EOF) {
	 switch(x) {
	 case 'T':
	      dying_time = time(NULL)+atoi(optarg) * 60;
	      break;
	 case 'c':
	      counter = atoi(optarg);
	      break;
	 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;
	 case 'u':
	      unlockonusr1 = 1;
	      break;
	 case 'S':
	      use_syslog = 1;
	      break;
	 default: 
	      usage(prog);
	      exit(1);
	 }
    }

    if(!eflag) iflag = 0;
    if (iflag) fstd=stderr;

    if (!time_mode) time_mode=5;

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

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

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

#ifdef HAVE_SYSLOG
    if(use_syslog) 
	 openlog ("NumberCruncher", LOG_PID, LOG_DAEMON);
#endif


    fprintf(fstd, "DIC Client v%s\n",VERSION);

#ifndef __EMX__
    nice(nicety);
#endif

    integrity(0);
    if (dying_time) fprintf(fstd, "This client will terminate at %s",
							ctime(&dying_time));
    if (dying_time && time(NULL)+time_mode >= dying_time) {
	time_mode=dying_time-time(NULL)+1;
    }
    iter = iterations(time_mode);

    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) {
	      
#ifndef __EMX__
	      setsid();
#endif

	      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
    }


    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);

	for (;;) {
        again:
	if (buf[0]=='K') {
	    userinfo("Client killed by server (%s)\n",buf);
	    exit(2);
	} else if (buf[0]=='R') {
	    if (!iter) iter=iterations(time_mode);
	    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) {
		userinfo("Got return receipt from server -- terminating.\n");
		exit(0);
            }
            /* what now ? send a H packet which is nearly a G packet */
	    if (!iter) iter=iterations(time_mode);
	    sprintf(msg,"H%.0f %d",iter,time_mode*3);
	    send_rep(msg);
	    goto again;
	}

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

        expected=time(NULL);
	expected += (((double)time_mode)*range/iter);
	userinfo("[%d:%.0f --> %.24s]\n",number, range, ctime(&expected));
	fflush(stdout);

	init(start,range,clear,cipher);

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

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

        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... */

/* lets think

   time_mode contains the expected timespan
   iter contains the expected range (as calculated)
   range contains the real range (as given by server)
   clock contains real time, for this range

   we want the new number of iterations to achieve time_mode delay,
   using the data of the current measurement, bound by 4/5 old data.

   1) calculate how many keys per second we did
   2) calculate how many keys per second were expected
   3) mix these figures 
      if the new speed is lower, mix them 3 new to 2 old
      if the new speed is higher, mix them 1 new to 4 old
      if the new speed is more than 10x higher than the old one,
      use 1.5 of the old speed only -- this keeps you below timeout
      if mis-measurement occured, or the chunk was too small.
   4) scale the speed to the number of seconds we want
   5) it really is easy, you just have to write it down!
*/
  {
    double old_kps, cur_kps, new_kps, span;
 
    old_kps=iter/(double)time_mode;
    span=tstop.tv_sec-tstart.tv_sec;
    span+=(tstop.tv_usec-tstart.tv_usec)/1.0e6;
    cur_kps=range/span;
    if (cur_kps < old_kps) {
	new_kps=(3.0*cur_kps+2.0*old_kps)/5.0;
    } else if (cur_kps > 3.5 * old_kps) {
	new_kps=1.5*old_kps;
    } else {
	new_kps=(cur_kps+4.0*old_kps)/5.0;
    }
    if (dying_time && time(NULL)+time_mode >= dying_time) {
	time_mode=dying_time-time(NULL)+1;
    }
    iter=bloody_floor(new_kps*(double)time_mode);
  }

#if 0
        /*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 > 10*old_iter) iter = 10 * old_iter;
#endif

        if (!byebye) byebye = exists(killfile);
	if (!byebye) if (counter) if (--counter==0) byebye=1;
	if (!byebye) if (dying_time && time(NULL) >= dying_time) byebye=1;

	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*3); 
	} else {
	    printf("Batch done in %3.2f minutes. " 
		   "Requesting new job with size %.0f\n", 
		   ((double)tstop.tv_sec-tstart.tv_sec)/60.0,iter);
	    sprintf(msg,"D%s %.0f %d",buf,iter,time_mode*3); 
	}
	send_rep(msg);

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

