/****************************************************
 Bsplus monitoring server:
 Incoming: (bsscomm -> monitor)
     "16clr 1 172.15.0.3" when server discover error
     "16add 1 172.15.0.3" for adding new host to array
     "03rst" to reset the host array
     "16del 2 172.15.0.3" for deleting
 Outgoing: (monitor -> bsscomm)
     "16clr 1 172.15.0.3"
     "16new 3 173.23.0.3"
     "03die" to tell BssComm when selection error
 Testing:
     Test new connection by "ping -w 2 -i 0.1 -c 5 172.18.0.3"
  
 ****************************************************/
#include<stdio.h>
#include<stdlib.h>

#include<sys/time.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<unistd.h>
#include<errno.h>
#include<ctype.h>
#include<string.h>
#include<time.h>

#define PORT 5354
#define QUEUESIZE 10
#define BUFMAX 4096

#define NO_OF_BSS 256
#define IP_LEN 16

struct connection {
    char ip[IP_LEN];
    int  bssid;
    int  connected;
    int  no_of_unreachable;
};
struct connection bsplus[NO_OF_BSS];
int nbsplus=0;        /* no of element in bsplus array*/
int newsock=-1;

void init_connection(struct connection *conn)
{
    conn->ip[0]='\0';
    conn->bssid=-1;
    conn->connected=0;
    conn->no_of_unreachable=0;
}

int process_outgoing_msg(int socket_fd, char *cmd, int index)
{
    char MsgBuffer[99]="\0";
    char temp[99]="\0";
    unsigned int len;

    sprintf(temp, "%s %d %s", cmd, (bsplus[index]).bssid, (bsplus[index]).ip);
    sprintf(MsgBuffer,"%.2d%s",strlen(temp),temp);
    
    len = write(socket_fd, MsgBuffer, strlen(MsgBuffer));
    if ( len != strlen(MsgBuffer) )
    {
        fprintf(stderr,"Error !!!");
    }

    return(len);
}

int process_incoming_msg(char *MsgBuffer)
{
    int i;
    int bssid;
    char cmd[4];
    char ip[IP_LEN]="\0";
    char buffer[BUFMAX]="\0";
    int retval=1;
    char new_str[4]="new";

    //printf("%s\n",MsgBuffer);
    if(!strncmp(MsgBuffer,"rst",3))
    {
        for(i=0;i<NO_OF_BSS;i++)
            init_connection(&bsplus[i]);
        nbsplus=0;
    }
    else
    {
        i=sscanf(MsgBuffer,"%s %d %s",cmd,&bssid,ip);
        //printf("cmd=%s bssid=%d ip=%s i=%d\n",cmd,bssid,ip, i);

        if ( (i!=3 ) || (bssid>=NO_OF_BSS) )
            return -1;
    
        if(!strncmp(cmd,"add",3))
        {
            (bsplus[nbsplus]).bssid = bssid;
            strcpy((bsplus[nbsplus]).ip, ip);
            (bsplus[nbsplus]).connected=0;
            nbsplus++;

            /* test connectivity immediately */
            sprintf(buffer,"ping -w 2 -i 0.1 -c 5 %s 2>/dev/null|grep time >>/dev/null",(bsplus[nbsplus-1]).ip);
 
            retval=system(buffer); 
            if (!retval) /* if ping succ */
            {
                process_outgoing_msg(newsock, new_str, nbsplus-1);//send "new"
                (bsplus[nbsplus-1]).connected = 1;
            }
        }
        else if(!strncmp(cmd,"del",3))
        {
            for(i=0;i<nbsplus;i++)
            {
                if( (bsplus[i]).bssid == bssid )
                {
                    nbsplus--;
                    (bsplus[i]).bssid = (bsplus[nbsplus]).bssid;
                    strcpy((bsplus[i]).ip, (bsplus[nbsplus]).ip);
                    (bsplus[i]).connected = (bsplus[nbsplus]).connected;
                    init_connection(&bsplus[nbsplus]);
                }
            }
        }
        else if (!strncmp(cmd,"clr",3))
        {
            // find index in bsplus[]
            for(i=0;i<nbsplus;i++)
                if( (bsplus[i]).bssid == bssid )
                    break;
            //process_outgoing_msg(newsock, clr_str, i);
            (bsplus[i]).connected = 0;
        }
        else
        {
            fprintf(stderr,"Undecoded msg: %s\n", MsgBuffer);
            return -1;
        }
    }
    return 0;
}

int main(int argc, char *argv[])
{
	char runMode;
    char buffer[BUFMAX];
    char msgbuffer[BUFMAX];
    struct sockaddr_in addr, caddr;
    int sock;
    int port,caddrlen;
    int nread;
    int msglen;
    int ret;
    int i,j=0;
    time_t last_t=0;
    time_t curr_t=0;
    int pingval;
    int nlast=0;
    char clr_str[4]="clr";

    struct timeval WaitTime;
    fd_set readfds;
    int width;

    /*if (argc == 1)
    {
        port=PORT;
    }
    else
    {
        port=atoi(argv[1]);
    }*/
    
    if (argc > 0) {
		runMode = getopt(argc, argv, "d");
		switch (runMode) {
		case 'd':
			setup_daemon();
			break;
		case '?':
			break;
		}
	}
	
	port=PORT;

    for (i=0;i<NO_OF_BSS;i++)
        init_connection(&bsplus[i]);

	sock=socket(PF_INET, SOCK_STREAM, 0);
    /* set family to internet */
    addr.sin_family=AF_INET;
    addr.sin_port=htons(port);
    addr.sin_addr.s_addr=INADDR_ANY;

    if( (bind(sock, (struct sockaddr *) &addr,sizeof(addr))) != 0)
    {
        sleep(60);
        if( (bind(sock, (struct sockaddr *) &addr,sizeof(addr))) != 0)
        {
      	    sprintf(buffer,"Cannot bind to socket %d",port);
            perror(buffer);
            fprintf(stderr,"Wait for one minute and try again\n");
            exit(errno);
        }
    }
    listen(sock, QUEUESIZE);

    newsock=accept(sock, (struct  sockaddr*) &caddr, &caddrlen);

    for(;;) {
    
        FD_ZERO(&readfds);

        if (newsock != -1) {
            FD_SET(newsock, &readfds);
            width = newsock + 1;
        } else {
            FD_SET(sock, &readfds);
            width = sock + 1;
        }

        WaitTime.tv_sec = 3;
        WaitTime.tv_usec = 0;
        ret=select(width,(fd_set*) &readfds, (fd_set*)0, (fd_set*)0, &WaitTime);
        if (ret==-1)
        {
            if(newsock != -1)
                write(newsock,"03die",5);
            perror("select() error");
            exit(errno);
        }
        if (ret == 0)
        {
            /* test BSS connectivity here */
            time(&last_t);
            for (;j<nbsplus;j++)
            {
                /* keep the poll interval be within 2 seconds */
                if (curr_t-last_t >= 2L )
                {
                    break;
                }

                sprintf(buffer,"ping -w 2 -i 1 -c 1 %s 2>/dev/null|grep time >>/dev/null",(bsplus[j]).ip);
                pingval=system(buffer);

                if (pingval) /* if ping failed */
                {
                    (bsplus[j]).no_of_unreachable++;

                    /*
                     * Too many ping failure (approx. 60 sec)
                     */
                    if ( (bsplus[j]).no_of_unreachable > 20 )
                    {
                        process_outgoing_msg(newsock, clr_str, j);
                        (bsplus[j]).connected = 0;
                    }
                }
                else  /* if ping succ */
                {
                    (bsplus[j]).no_of_unreachable=0;

                    process_outgoing_msg(newsock, "new", j);
                    (bsplus[j]).connected = 1;
                }
                time(&curr_t);
            }   /* end_of_for  */
            if (j>=nbsplus)
                j=0;
            continue;    
        }

        if (FD_ISSET(sock, &readfds))
        {
            if (newsock==-1)
            {
                newsock=accept(sock, (struct  sockaddr*) &caddr, &caddrlen);
                width = newsock + 1;
            }
        }
        if (FD_ISSET(newsock, &readfds))
        {
            nread = read(newsock, msgbuffer+nlast, 2-nlast);

            if (nread<=0) {
                FD_CLR(newsock, &readfds);

                close(newsock);
                newsock=-1;
                width = sock + 1;
                for (i=0;i<nbsplus;i++)
                    init_connection(&bsplus[i]);
                nbsplus=0;
            }
            else if (nread < 2-nlast) /* Message partly read */
            {
                nlast += nread;
                fprintf(stderr,"Read header failed\n");
            }
            else if (nread == 2-nlast)
            {
				nlast = 0;
                if (!isdigit(msgbuffer[0]) || !isdigit(msgbuffer[1]) )
                    continue;
            
                msgbuffer[2]='\0';
                msglen=atoi(msgbuffer);
                nread = read(newsock, msgbuffer, msglen);
                if ( nread == msglen )
                {
                    msgbuffer[msglen]='\0';
                    process_incoming_msg(msgbuffer);
                }
                else
                {
                    fprintf(stderr,"Wrong header length %d\n",msglen);
                }
            }
            else /* Message read error */
            {
                fprintf(stderr,"Read header failed\n");
            }
        }   /* End_of_if (FD_ISSET */
    }       /* End_of_for(;;) */
}

void setup_daemon(void)
{
	/*
	int i;
	
	for (i = 0; i < 3; ++i) {
		close(i);
	}
	*/
	switch (fork()) {
	case -1:
		perror("setup_daemon(), 1st fork()");
		exit(2);
	default:
		exit(0);
	case 0:
		if (setsid() == -1) {
			perror("setup_daemon(), setsid()");
			exit(3);
		}
		switch (fork()) {
		case -1:
			perror("setup_daemon(), 2nd fork()");
			exit(3);
		default:
			exit(0);
		case 0:
			umask(0);
			/* and return with daemon set up */
			return;
		}
	}
}

