/****************************************************************
 *                                                              *
 *  LIBDIST V1.0						*
 *                                                              *
 *  elec.c -- election algorithm                                *
 *                                                              *
 *  Last changed: 19.02.96                                      *
 *  Author: Frank Kargl (frank.kargl@informatik.uni-ulm.de)     *
 *                                                              *
 *  Restrictions: works only for Solaris 2.6 or above           *
 *                                                              *
 ****************************************************************/

#include "libdist.h"
#include "config.h"

#include <string.h>		/* for memset */
#include <time.h>		/* for select timeout */
#include <sys/types.h>		/* for select */
#include <arpa/inet.h>		/* for inet_* routines */
#include <signal.h>		/* for signals */

/***
 *** int dl_el_startele(char *service)
 *** 
 *** Function: start an election for all clients on the port specified
 ***           by service
 *** Return  : DL_OK if success
 ***           DL_ERROR if error
 *** Restrictions: note that a client must also be running
 ***               for this process to participate in the election
 ***/

int dl_el_startele(char *service) {

    int bsock;			/* send socket */
    char buffer[DL_MAXLINE];	/* send buffer */

    /* text to send. elections always start at 0 */
    strcpy(buffer,"E 0 0\r\n");

    /* open broadcast socket */
    bsock=dl_sck_sockb();
    if (bsock==DL_ERROR) {

#ifdef DEBUG
	fprintf(stderr,"LIBDIST-ele: error opening socket\n");
#endif

	return DL_ERROR;
    }

    if (dl_sck_sendb(bsock,service,buffer) == DL_ERROR) {
    /* error ? how could this happen ? */

#ifdef DEBUG
	fprintf(stderr,"LIBDIST-ele: error sending text\n");
#endif

	return DL_ERROR;
    }

    /* close socket */
    dl_sck_close(bsock);

    return DL_OK;
}

/***
 *** thread_t dl_el_startserver(char *service, \
 ***			void(*function)(char *winner,int winport, int myport))
 ***
 *** Function: start a server for the election port specified by service
 ***           that is participating in elections from now on
 ***           after an election function is called with the winner as
 ***           argument
 *** Return  : the thread id of the server
 ***           DL_ERROR if error
 ***/

static struct arg_struct {	/* needed for passing all the data to thread */
    char service[20];
    void (*function)(char *winner,int winport, int myport);
} arg;

thread_t dl_el_startserver(char *service, \
		    void(*function)(char *winner,int winport, int myport)) {

    thread_t mythread;			/* thread id of newly created thread */
    void *dl_el_server(void *arg);	/* function to start thread with */

    /* pack arguments into structure */
    strcpy(arg.service,service);
    arg.function=function;
    
    /* start thread */
    mythread = dl_thr_create(dl_el_server,(void *)&arg);

    /* and exit */
    return mythread;
}

/***
 *** int dl_el_stopserver(thread_t server)
 ***
 *** Function: stop the specified server from participating
 ***           in elections
 *** Return  : DL_OK if success
 ***	       DL_ERROR if error
 ***/

int dl_el_stopserver(thread_t server) {
    return(dl_thr_kill(server,DL_EL_SIGNAL));
}

/***
 *** void dl_el_exitserver(int signal)
 ***
 *** Function: stop the election server
 *** Return  : -
 ***/

static int myport;		/* port for socket ; needed global for */
				/* use in signal handler, sorry ;-)  */
static int intport;		/* the port of the distributor ; also global */

void dl_el_exitserver(int signal) {

    struct sockaddr_in localhost;	/* an IEA */
    char buffer[DL_MAXLINE];		/* send buffer */
    int sock;				/* send socket */ 

    /* unsubscribe with distributor */

    localhost.sin_addr.s_addr=INADDR_LOOPBACK;
    localhost.sin_family=AF_INET;
    localhost.sin_port=intport;

    /* create communication socket */
    sock=socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP);

    /* message to send */
    sprintf(buffer,"R %d\r\n",myport);

    /* here it goes */
    sendto(sock,buffer,strlen(buffer)+1,0, \
	    (struct sockaddr *)&localhost,sizeof(localhost));
    
    /* and exit */
    dl_thr_exit();
}

/***
 *** void *dl_el_server(void *arg)
 ***
 *** Function: the election server. wait for and parse requests
 *** Return  : values != NULL indicate an error
 ***           detailed return errors to be specified when needed
 ***/

void *dl_el_server(void *arg) {

    char service[20];	/* the port for internal use ; as string */
    void(*function)();	/* the function to call when election is over */
    int distsendsock;	/* socket used for sending to distributor */
    int distrecsock;	/* socket used for receiving from distributor */
    int bsock;		/* socket used for broadcasting */
    int s;		/* socket used for various purposes */
    struct sockaddr_in dist_sin;	/* an IEA */
    struct sockaddr_in todist;		/* an IEA */
    struct sockaddr_in *sin_ptr;	/* an IEA ptr */
    fd_set sockfds;		/* file descriptor set for select */
    int maxsockfds=0;		/* maximum descriptor used in sockfds */
    struct timeval timeout;	/* timeout with select */
    int has_data;		/* number of descriptors from select */
    int election;		/* flag election is running */
    int winflag;		/* flag can we still become the winner */
    struct arg_struct *argptr;	/* I hate conversions ;-) */
    unsigned int ipsum,myipsum;	/* IP numbers used in election */
    unsigned int ipport;	/* IP ports used in election */
    char buffer[DL_MAXLINE];	/* I/O buffer string */
    int reclen;			/* return length from recv */
    char winner[20];		/* winner of election */
    int winport;		/* winner of election */

    /* get all the arguments back */
    argptr=(struct arg_struct *)arg;
    strcpy(service,argptr->service);
    intport=dl_sck_getport(argptr->service,DL_SCK_UDP);
    function=argptr->function;

    /* check if distributor is allready running */
    s=dl_sck_server(service,DL_SCK_UDP,NULL);
    if (s != DL_ERROR) {
	dl_sck_close(s);

	/* start distributor */
	/* we use fork here, because the distributor shouldn't be */
	/* tied to any specific process */
	switch(fork()) {

	    /*** child ***/

	    case 0:

#ifdef DEBUG
		    fprintf(stderr,"LIBDIST-ele: launching distributor\n");
#endif

		    execl("distrib","distrib",service,NULL);
		    /* you'll never return from here */

		    exit(0);

	    /*** parent ***/

	    default:

		    /* wait a bit so the distributor has time to start */
		    sleep(2);
	}

    } else {

#ifdef DEBUG
	fprintf(stderr,"LIBDIST-ele: distributor allready running\n");
#endif

    }

    /* create broadcast socket */
    bsock=dl_sck_sockb();
    if (bsock == 0) {

#ifdef DEBUG
	fprintf(stderr,"LIBDIST-ele: can't open broadcast socket\n");
#endif

	dl_thr_exit();
    }

    /* now create the distributor sockets		*/
    /* need to do this by hand because sin is needed	*/

    distsendsock=socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP);
    if (distsendsock == -1) {

#ifdef DEBUG
	fprintf(stderr,"LIBDIST-ele: can't create distsendsock\n");
#endif

	dl_thr_exit();
    }

    /* where to send from todist */
    todist.sin_addr.s_addr	= INADDR_LOOPBACK;
    todist.sin_family		= AF_INET;
    todist.sin_port		= intport;

    distrecsock=socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP);
    if (distrecsock == -1) {

#ifdef DEBUG
	fprintf(stderr,"LIBDIST-ele: can't create distrecsoc\n");
#endif

	dl_thr_exit();
    }

    /* now scan for a free port and bind */
    dist_sin.sin_family		= AF_INET;
    dist_sin.sin_addr.s_addr	= INADDR_LOOPBACK;
    myport			= DL_EL_PORTB;
    dist_sin.sin_port		= htons(myport);

    while (bind(distrecsock,(struct sockaddr *)&dist_sin, \
		sizeof(dist_sin)) == -1 &&	/* bind sucessfull ? */
	   myport < DL_EL_PORTB + 1000 &&	/* we only scan 1000 ports */
	   myport < 65534 ) {			/* and only up to maximum */
	myport++;
	dist_sin.sin_port = htons(myport);
    }

    /* error ? */
    if (myport >= DL_EL_PORTB + 1000 || myport >= 65534) {

#ifdef DEBUG
	    fprintf(stderr,"LIBDIST-ele: can't bind socket\n");
#endif

	    dl_thr_exit();
    }

    /* and yet another socket for calculationg the election data */
    s=socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP);

    /* calculate my own data */
    sin_ptr=dl_sck_getifadr();
    myipsum	= sin_ptr->sin_addr.s_addr;

    close(s);

#ifdef DEBUG
    fprintf(stderr,"LIBDIST-ele: myipsum = %x  -  myport = %d \n",myipsum,myport);
#endif

    /* now register with distributor */
    sprintf(buffer,"A %d\r\n",myport);
    sendto(distsendsock,buffer,strlen(buffer)+1,0, \
	    (struct sockaddr *)&todist,sizeof(todist));
    
    /* routine for exiting server */
    dl_sig_catch(DL_EL_SIGNAL,dl_el_exitserver);

    /* prepare everything for select */
    timeout.tv_sec	= (DL_EL_TIMEOUT) / 1000;
    timeout.tv_usec	= (DL_EL_TIMEOUT) % 1000;
    maxsockfds=((distrecsock+1) > maxsockfds ? (distrecsock+1) : maxsockfds);

    /* init values */
    election=0;
    winflag=1;

    /* wait for elections */
    while(1) {

	/* prepare for next select */
	FD_ZERO(&sockfds);
	FD_SET(distrecsock,&sockfds);

	/* check if received data pending */

#ifdef DEBUG
	fprintf(stderr,"LIBDIST-ele: vor select\n");
#endif

	has_data = select(maxsockfds,&sockfds, \
			    (fd_set *)NULL,(fd_set *)NULL,&timeout);

#ifdef DEBUG
	fprintf(stderr,"LIBDIST-ele: select returned %d\n",has_data);
#endif

	/* check result of select */
	switch (has_data) {

	    /*** timeout ***/

	    case 0:

#ifdef DEBUG
		    fprintf(stderr,"LIBDIST-ele: select timeout\n");
#endif


		    /* are we doing an election ? */
		    if (election) {

			/* timeout in election */
			/* -> no one is answering any more */
			/* -> we could be the winner */

			/* election is over */
			election=0;

			/* if we could be winner, then we really are winner */
			if (winflag) {
			    sprintf(buffer,"W %s %u\r\n",inet_ntoa(sin_ptr->sin_addr),myport);
			    dl_sck_sendb(bsock,service,buffer);
			}
		    }

		    /* now continue with next read */
		    break; /* from switch (has_data) */
		    
	    /*** there's a message waiting for us ***/

	    case 1:

		    /* get message */
		    reclen=recv(distrecsock,buffer,sizeof(buffer),0);

		    if (reclen<=0) {

#ifdef DEBUG
			fprintf(stderr,"LIBDIST-ele: recv error");
#endif

			break; /* from switch (has_data) */
		    }

#ifdef DEBUG
		    fprintf(stderr,"LIBDIST-ele: received #%s#\n",buffer);
#endif

		    /* what kind of message */
		    switch(buffer[0]) {

			/* Election 'E ipsum port' */

			case 'E':

				/* parse message */
				if (sscanf(buffer,"E %d %d\r\n",&ipsum,&ipport) != 2) {

#ifdef DEBUG
				    fprintf(stderr,"LIBDIST-ele: wrong message format\n");
#endif

				    break; /* from switch (buffer[0]) */
				}

				/* if we can't win anymore, stop here */
				if (winflag==0 && ipsum!=0 && ipport!=0) {

#ifdef DEBUG
				    fprintf(stderr,"LIBDIST-ele: discarding message #%s#\n",buffer);
#endif

				    break; /* from switch (buffer[0]) */
				}

				/* now we are doing an election */
				election=1;

				/* modified bully election algorithm */
				/* should I participate in election anymore ? */
				if ((myipsum > ipsum) || \
				    (myipsum == ipsum && myport > ipport)) {

#ifdef DEBUG
				    fprintf(stderr,"LIBDIST-ele: continuing with election\n");
				    fprintf(stderr,"LIBDIST-ele: myipsum = %u\n",myipsum);
				    fprintf(stderr,"LIBDIST-ele: ipsum = %u\n",ipsum);
				    fprintf(stderr,"LIBDIST-ele: myport = %u\n",myport);
				    fprintf(stderr,"LIBDIST-ele: ipport = %u\n",ipport);
#endif

				    /* send new election string */
				    
				    /* ATTENTION: as the UDP packets could */
				    /* get lost without notification, our  */
				    /* election would totally mess up, if  */
				    /* this happens. So you better use it  */
				    /* only with reliable networks         */
   
				    sprintf(buffer,"E %u %u\r\n",myipsum,myport);
				    dl_sck_sendb(bsock,service,buffer);

				 } else if (myipsum==ipsum && myport==ipport) {
				     /* sometimes we get phantom messages */
				     /* really dont know where they       */
				     /* come from, this is only a         */
				     /* work-around                       */
				     break;
				 } else {
				    /* as we don't participate any more	   */
				    /* we will never be the winner of this */

#ifdef DEBUG
				    fprintf(stderr,"LIBDIST-ele: retreating from election\n");
#endif

				    winflag=0;
				}

				break; /* from switch (buffer[0]) */

			/*** Winner 'W ip-addr port' ***/

			case 'W':

				/* parse message */
				if (sscanf(buffer,"W %s %d\r\n",winner,&winport) != 2) {

#ifdef DEBUG
				    fprintf(stderr,"LIBDIST-ele: wrong message format\n");
#endif

				    break; /* from switch (buffer[0]) */
				}

				/* call function with argument winner */

				function(winner,winport,myport);

				/* election time is over, reset flags */
				election=0;
				winflag=1;

				break; /* from switch (buffer[0]) */

			/*** Default -> Protocol error ***/

			default:

#ifdef DEBUG
				fprintf(stderr,"LIBDIST-ele: protocol error\n");
#endif

		    } /* switch (buffer[0]) */

		    break; /* from switch (has_data) */

	    /*** Default -> select error ***/

	    default:

#ifdef DEBUG
		    fprintf(stderr,"LIBDIST-ele: select error\n");
#endif

	} /* switch (has_data) */
    } /* while */
}
