/************************************************************/
/*                                                          */
/*  The cpp file of extedned relayer program                */
/*  For CS 145 (Fall 2002) Lab 4                            */
/*  Author: Xiaoliang (David) Wei                           */
/*  Date:   Nov 26, 2002                                    */
/*                                                          */
/************************************************************/


/*
Command Line format:
relayer <port1> <IP2> <port2> <dropping_rate> <dropping_mode>

<port1>: the port that the relayer is listening to.
<IP2>:<port2> The address that the incoming packets are sent to.
<dropping_rate>: packet drop rate. It should be larger or equal to 1. Approximately, the relayer will drop 1 packet out of <dropping_rate> packets. 
<dropping_mode>: the mode that the packet is dropped. It can be 1, 2 or 3:
1: Random dropping: Drop packet randomly
2: Interval dropping: Drop 1 packet for every <dropping_rate> packets
3: Continuous dropping: Drop first <dropping_rate> packets for every <dropping_rate>*<dropping_rate> packets.f

*/

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/time.h>
#define TRUE 1
#define MAX_CON_BUF 8192

#define RANDOM 1
#define INTERVAL 2
#define CONTINUOUS 3

unsigned short int udpport;
int udpsock;

int nextSock;
struct sockaddr_in nextServer;

int rate;
int counter;
int behaviour;

char nextIP[100];
int sendInit(unsigned short int nextPort,char* nextHost)
{
  struct hostent *hp;
  char buf[MAX_CON_BUF];
  int rval;
  int i,j,k,tmp;

  
  nextSock=socket(AF_INET,SOCK_DGRAM,0);
  if (nextSock<0)
  {
	printf("ERROR:opening stream socket\n");
	return 1;
  }
  nextServer.sin_family=AF_INET;
    
  hp=gethostbyname(nextHost);
  if (hp==0)
  {
	printf("ERROR: %s:unknown host\n",nextHost);
	return 2;
  }

  memcpy((char *)&nextServer.sin_addr,(char *)hp->h_addr,hp->h_length);
  nextServer.sin_port=htons(nextPort);
}

int send(char * buf, int length)
{
	return sendto(nextSock,buf,length,0,(struct sockaddr *)&nextServer,sizeof nextServer);	      
}


int udp_init(unsigned short int port)
{
    int length;
    struct sockaddr_in server;   	
    udpsock=socket(AF_INET,SOCK_DGRAM,0);
    if (udpsock<0)
    {
 	 printf("ERROR: opening stream socket!\n");
   	exit(1);
    }

    server.sin_family=AF_INET;
    server.sin_addr.s_addr=INADDR_ANY;
    server.sin_port=htons(port);
    if (bind(udpsock,(struct sockaddr *)&server,sizeof server)<0)
    {
    	printf("UDP Binding error, maybe port number occupied by others. Change another port");
   	exit(1);
    };
    length=sizeof server;
    if (getsockname(udpsock,(struct sockaddr *)&server,(socklen_t *)&length)<0)
    {
	 printf("ERROR: getting socket name\n");
   	exit(1);
    }
    printf("UDP Socket port # %d\n",ntohs(server.sin_port)); 
    return 0;
};

int shouldRelay()
{
	struct timeval now;
	int random;
	switch (behaviour)
	{
		case RANDOM:
   	 		gettimeofday(&now,NULL);
   	 		random=now.tv_usec%1000;
   	 		if ((random*rate)<1000) return 0; else return 1;
		break;
		case INTERVAL:
			counter++;
			if (counter>=rate)
			{
				counter=0;
				return 0;
			}
			else
				return 1;
			
		break;
		case CONTINUOUS:
			counter++;
			if (counter>=rate*rate) counter=0;
			if (counter<rate) return 0; else return 1;
		break;
		default:
			return 1;
	}
}

void udp_talker()
{
   struct sockaddr incoming_addr;
   socklen_t incoming_len;   
	
   char s[MAX_CON_BUF];
   int rval; 		
   
   memset(s,0,sizeof s);   
   incoming_len=sizeof incoming_addr;
   rval=recvfrom(udpsock,s,MAX_CON_BUF,0,&incoming_addr, &incoming_len);   
   if (rval<=0)
   	printf("ERROR: in reading UDP socket\n");
   else  
   { 
	   printf("Get %d characters\n", rval);
	   if (shouldRelay())
	   {
	   	rval=send(s,rval);
	   	if (rval<=0) 
	   	{
	   		printf("ERROR: in sending UDP packets\n");
	   		printf("%d\n",rval);
	   	}
	   	else
	   		printf("relayed\n");
	   }
	   else
	   	printf("dropped\n");
    }	
};

char* gets(int behaviour)
{
	switch (behaviour)
	{
		case RANDOM:
		   return "RANDOM";
		break;
		case INTERVAL:
		   return "INTERVAL";			
		break;
		case CONTINUOUS:
		   return "CONTINUOUS";
		break;
		default:
			return "NO DEFINITION";
	}	
}

void control()
{
	int tmprate;
	int tmpbehaviour;
	int nextPort;
	int ok=0;
	char a;

	scanf("%c",&a);
	if ((a!='p')&&(a!='r')&&(a!='m')) return;
	printf("Entering the control mode:\n");
	while (!ok)
	{
		printf("Current set up:\n");
	   	printf("Listen to %d, Relay to %s:%d with dropping rate %d and dropping mode:%s\n",
	   	udpport, nextIP, ntohs(nextServer.sin_port), rate, gets(behaviour)
	   	);	
		if (a=='r')
		{
		printf("Please input the new dropping rate:");
		scanf("%d",&tmprate);
		   if ((tmprate<1)) 
		   {
		   	printf("Error: Dropping rate is wrong, it should be larger or equal to 1\n");
		   	continue;
		  };
		  rate=tmprate;
		}
		else
		if (a=='m')
		{
		printf("Please input the new dropping mode [1:RANDOM 2:INTERVAL 3:CONTINUOUS]:");
		scanf("%d",&tmpbehaviour);
		if ((tmpbehaviour<1) || (tmpbehaviour>3)) 
		{
			printf("Error: Behaviour code is wrong. It should be 1 (random) 2(interval) or 3(continous)\n");
		   	continue;
		};
		behaviour=tmpbehaviour;
		}
		else
		if (a=='p')
		{	
		printf("Please input the new next port:");
		scanf("%d",&nextPort);
		nextServer.sin_port=htons(nextPort);		
		};
		ok=1;
	};
	printf("New setup: \n");
	printf("Listen to %d, Relay to %s:%d with dropping rate %d and dropping mode:%s\n",
   	udpport, nextIP, ntohs(nextServer.sin_port), rate, gets(behaviour)
   	);
   	counter=0;
   	
}


int main (int argc,char *argv[])
{
   /* port nextIP nextPort dropping_rate behaviour */
   fd_set setForSelect;
   
   counter=0;
   behaviour=atoi(argv[5]);
   rate=atoi(argv[4]);
   if ((rate<1)) 
   {
   	printf("Error: Dropping rate is wrong, it should be larger or equal to 1\n");
   	return 1;
   };
   if ((behaviour<1) || (behaviour>3)) 
   {
   	printf("Error: Behaviour code is wrong. It should be 1 (random) 2(interval) or 3(continous)\n");
   	return 1;
   };
   udpport=atoi(argv[1]);  
   if (udp_init(udpport)!=0) { printf("UDP Socket establish failure!\n"); return 1;};
   sendInit(atoi(argv[3]),argv[2]);
   printf("Listen to %d, Relay to %s:%d with dropping rate %d and dropping mode:%s\n",
   udpport, argv[2], ntohs(nextServer.sin_port), rate, gets(behaviour)
   );
   strcpy(nextIP,argv[2]);
   printf("Listening...\n");
   do 
   { 
	 FD_ZERO(&setForSelect);
   	 FD_SET(udpsock,&setForSelect);   	 
   	 FD_SET(0,&setForSelect);
   	 select(udpsock+1,&setForSelect,0,0,NULL);
   	 if (FD_ISSET(udpsock,&setForSelect))
	 {
	 	udp_talker();
	 }
	 if (FD_ISSET(0,&setForSelect))
	 {
		printf("Relaying halt\n");
	 	control();
		printf("Relaying resume\n");
	 }
    }while(TRUE);
};


