/*
 *  throttle.c
 *  
 *
 *  Created by Piero Rivera Benois & Nikolas Skoljarev Navarro on 13-10-09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <sys/time.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

/*
Se definen valores por defecto del tamaño del balde y de 
la tasa de transferencia
*/
#define MAX_BURST 1500
#define INITIAL_LEAKY_RATE 128

int errno;

// Se definen valores iniciales de balde y tasa de salida de datos.
int BLevelCtoS=0;//Nivel del balde de entrada
int BLevelStoC=0;//Nivel del balde salida
int bucketSize= MAX_BURST;
int leakingRate= INITIAL_LEAKY_RATE;

//Se definen e inicializan las variables de condición y de exclusión mutua para el manejo de hebras.
pthread_mutex_t BLockCtoS = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t BLockStoC = PTHREAD_MUTEX_INITIALIZER;

pthread_cond_t BChange_CtoS = PTHREAD_COND_INITIALIZER;
pthread_cond_t BChange_StoC = PTHREAD_COND_INITIALIZER;

//Sockets
int s_client, s_serv;
int d_client;//Descriptor del socket del cliente, luego de aceptada la conexión

/////////////////////////////////////
/////////FUNCIONES///////////////////
/////////////////////////////////////

/////////leak/////////////////////////
/*
Esta función es la mediadora entre ambos sockets, permite que 
se estén enviando y recibiendo datos a una tasa constante, esta
tasa debe ser especificada por consola.
*/
void * leak(void * arg)
{
  while (1) {

    sleep(1); //Tiempo de envío
//Control datos desde el Cliente al Servidor
    if(pthread_mutex_trylock(&BLockCtoS)==0)//Se intenta acceder al balde de C->S
    { 
    if (BLevelCtoS>0) //Si el balde de C->S tiene paquetes pendientes
      BLevelCtoS-=leakingRate; //Decremento el nivel del balde según la tasa de envío
    pthread_mutex_unlock(&BLockCtoS); //Se desbloquea el mutex de entrada
    pthread_cond_signal(&BChange_CtoS);//Se da un signal a la variable de condicion, anunciando que el bucket ha cambiado
    } 

//Control datos desde el Servidor al Cliente
    if(pthread_mutex_trylock(&BLockStoC)==0)//Se intenta acceder al balde de S->C
    { 
    if (BLevelStoC>0) //Si el balde de salida tiene paquetes pendientes
      BLevelStoC-=leakingRate; //Decremento el nivel del balde según la tasa de envío
    pthread_mutex_unlock(&BLockStoC); //Desbloqueo el mutex de salida
    pthread_cond_signal(&BChange_StoC);//Se da un signal a la variable de condicion, anunciando que el bucket ha cambiado
	} 
}
}

//////////////////prompt/////////////////
/*
Prompt que permite cambiar los valores de la tasa y el balde 
mediante consola
*/
void * prompt(void * arg){
        printf("\nIngrese valores de balde y tasa separados por espacio, ambos en bytes, \nseguidos de un retorno de carro\n");
        printf("Ingrese 0 0 para terminar\n");
        while( 1 )
	{
        if(scanf("%d %d",&bucketSize,&leakingRate)&&(bucketSize<=leakingRate)){
        printf("Los nuevos valores del balde y tasa son:\nBucket: %d[byte] Tasa: %d[byte]\n", bucketSize, leakingRate);}
        else { getchar();}
        if(bucketSize==0 && leakingRate==0) 
        {printf("Ha ingresado parámetros para salir del programa\nSaliendo...\n");exit(0);}
	}

}

////////////////clientToServer////////////
/*
Esta función procura enviar datos del socket cliente
al socket servidor según el tamaño de balde y la tasa
de envío, hace una especie de tunel regido por los valores
ingresados por consola.
*/
void * clientToServer(void * arg){
	int aux;
	int out=0;
	int buck;
	char buff;
	printf("C->S\n");
	
        while(1){
	buck=bucketSize;
	pthread_mutex_lock(&BLockCtoS); //Si el balde de C->S está disponible lo tomo y lo bloqueo
    	while ( BLevelCtoS >= buck) /* Siempre y cuando el bucket esté lleno */
      	pthread_cond_wait(&BChange_CtoS, &BLockCtoS); //Se espera que cambie el bucket asociado al mutex, es decir, 
								//que se envien los datos que no caben en el balde a la tasa especificada.
    	BLevelCtoS+=1; //Incremento el nivel del balde
    	pthread_mutex_unlock(&BLockCtoS); //Se desbloquea la variable de mutua exclusión
	//
        aux=(int)recv(d_client, &buff, (size_t)sizeof(buff), 0);
		switch (aux){
			case 0:
				printf("the client has closed its half side of the connection \n");
				out=1;
				break;
			case -1:
				printf("an error occurred on recv(from_client)\n");
				break;
			default:
				//printf("%d bytes received from client\n",aux);
				break;
		}
			
    	if(out) break;/*Envío simpre y cuando no se halla cerrado la conexión*/
		aux=(int)send(s_serv,&buff,(size_t)aux,0);
		switch (aux){
			case -1:
				printf("an error occurred on send(to_server)\n");
				break;
			default:
				//printf("%d bytes sent from client to server\n",aux);
				break;
		}
}
	close(d_client);
	printf("TERMINO DE CLIENT TO SERVER %d\n", aux);
}

////////////////serverToClient////////////
/*
Esta función procura enviar datos del socket servidor
al socket clientre según el tamaño de balde y la tasa
de envío, hace una especie de tunel regido por los valores
ingresados por consola.
*/
void * serverToClient(void * arg){
	int aux;
	int out=0;
	int buck;
	char buff;
	printf("S->C\n");

	while(1){
	buck=bucketSize;
	pthread_mutex_lock(&BLockStoC); //Si el balde de entrada está disponible lo tomo y lo bloqueo
    	while ( BLevelStoC >= buck) /* Siempre y cuando el bucket esté lleno */
      	pthread_cond_wait(&BChange_StoC, &BLockStoC); //Se espera que cambie el bucket asociado al mutex, es decir, 
								//que se envien los datos que no caben en el balde a la tasa especificada.
    	BLevelStoC+=1; //Incremento el nivel del balde
    	pthread_mutex_unlock(&BLockStoC);//Se desbloquea la variable de mutua exclusión


		aux=(int)recv(s_serv, &buff, (size_t)sizeof(buff), 0);
		switch (aux){
			case 0:
				printf("the server has closed its half side of the connection \n");
				out=1;
				break;
			case -1:
				printf("an error occurred on recv(from_server)\n");
				break;
			default:
				//printf("%d bytes received from server\n",aux);
				break;
		}	

		if(out) break;
		aux=(int)send(d_client,&buff,(size_t)aux,0);
		switch (aux){
			case -1:
				printf("an error occurred on send(to_client)\n");
				break;
			default:
				//printf("%d bytes sent from server to client\n",aux);
				break;
		}
	
	}
	close(s_serv);
	printf("TERMINO DE SERVER TO CLIENT %d\n", aux);

}

int main(int argc, char *argv[])
{
    int p1, p2;
    void *rompe;
    int n;//n->largo en bytes buffer recibido
    unsigned int len;//len->Tamaño en memoria estructura
    char hostname[64];//Arreglo donde se almacena la dirección del host a conectarse
    struct hostent *hp;//Estructura con información sobre la máquina
    struct sockaddr_in add_c;//Estructura con información acerca del socket cliente
    struct sockaddr_in add_s;//Estructura con información acerca del socket servidor
    pthread_t stc_thread, cts_thread, prom_thread, leak_thread;

/*
Se revisan los argumentos ingresados
*/
    switch(argc) {
        case 3: //throttle  puerto_escucha puerto_remoto
            /* Get our local host name.  */
          gethostname(hostname, sizeof(hostname));
          p1=atoi(argv[1]);
          p2=atoi(argv[2]);
          break;

        case 4: //throttle puerto_escucha [host_remoto] puerto_remoto
            /* Get name from command line */
          strcpy(hostname, argv[2]);
          p1=atoi(argv[1]);
          p2=atoi(argv[3]);
          break;

        default:
          printf("La sintaxis correcta para llamar a este programa es: \"throttle puerto [host_remoto] puerto_remoto\"\n"); 
          return -1;
    }

    /*
	 SOCKET AL CLIENTE. 
	 */
    s_client = socket(AF_INET, SOCK_STREAM, 0);//Socket TCP, elige protocolo por defecto
    /* Se crea la dirección del socket que escucha en el localhost. */
    add_c.sin_family = AF_INET;
    add_c.sin_port = htons(p1); // Puerto de escucha es el definido como p1
    add_c.sin_addr.s_addr = htonl(INADDR_ANY); //El socket recibe paquetes desde cualquiera de las interfaces del computador
    len = sizeof(struct sockaddr_in);
    /* Se une el socket socket a la dirección del cliente */
    if( bind(s_client, (struct sockaddr *) &add_c, len)){
        printf("bind(client) error\n");
		return -2;
	}

	
    /* Se escucha por alguien que desee conectarse a este socket.  */
    if(listen(s_client, 5)){
		printf("listen() error\n");
		return -3;
	}
    
    /*
	 SOCKET AL SERVIDOR.
	 */
    /* Se direcciona el host local.*/
    hp = gethostbyname(hostname);
    /*se crea el socket hacia el exterior.*/
    s_serv= socket(AF_INET, SOCK_STREAM, 0);
    /*Se direcciona el socket */
    add_s.sin_family = AF_INET;
	add_s.sin_port = htons(p2);
    memcpy(&add_s.sin_addr, hp->h_addr_list[0], hp->h_length);

	/*
	 CONEXIONES
	 */
    /*Se acepta conexion proveniente del lado del cliente*/
    if((d_client = accept(s_client, (struct sockaddr *) &add_c, &len))<0){
	   printf("accept(client) error, error nº %i, descripción: %s\n", errno, strerror(errno));
	   return -4;
	   }
    printf("client: conexion desde: %s \n", inet_ntoa(add_c.sin_addr));	

	/* Se conecta al socket servidor */
    if((connect(s_serv, (struct sockaddr *) &add_s, len))!=0){
		printf("connect(s_serv) error, error nº %i, descripción: %s\n", errno, strerror(errno));
		return -5;
	}
    printf("server: conexion desde: %s \n", inet_ntoa(add_s.sin_addr));


/*
Hebras donde existe concurrencia hasta el momento de los 4 hilos, 
la primera que procura coordinar la tasa de envío a las dos hebras 
que procuran dar bidireccionalidad a los sockets, y 
la última que actualiza los datos del balde cuando se ingresan 
dos números en el prompt
*/
	
	printf("A continuación se crearán las hebras\n");
int err;

   if (pthread_create(&leak_thread, NULL, leak, NULL)){
		printf("pthread_create()1 error\n");
		return -6;
	}


   if (pthread_create(&cts_thread, NULL, clientToServer, NULL)){
		printf("pthread_create()1 error\n");
		return -6;
	}

    if (pthread_create(&stc_thread, NULL, serverToClient, NULL)){
        printf("pthread_create()2 error\n");
        return -6;
	} 

    if (pthread_create(&prom_thread, NULL, prompt, NULL)){
        printf("pthread_create()3 error\n");
        return -6;
	}

    if (pthread_join(leak_thread, &rompe))
		printf("Can't join leakBucket thread.\n");

    if (pthread_join(cts_thread, &rompe))
		printf("Can't join leakBucket thread.\n");

    if (pthread_join(stc_thread, &rompe))
		printf("Can't join server thread.");

    if (pthread_join(prom_thread, &rompe))
		printf("Can't join client thread.\n");	


    return 0;
}
