/**
	@file		tcp.c
	@brief 		File containing the source code of tcp protocol packet functions
	@author		Adrían Antoñana Gómez
	@author		Carlos Díaz Cánovas
	@version	beta-2009/01/10

*/
#include "tcp.h"
#include "msg.h"

#define TCP_TIMEOUT 5000

/**
@verbatim
This is the state in the state machine of TCP:

1	=>	CLOSED_LISTEN
2	=>	SYN_SENT
3	=>	SYN_RECV
4	=>	ESTABLISHED
5	=>	FIN_WAIT1
6	=>	FIN_WAIT2
7	=>	TIME_WAIT
8	=>	CLOSE_WAIT
9	=>	LAST_ACK
10	=>	CLOSED

0	=>	ERROR
@endverbatim
*/


uint8_t state = CLOSED_LISTEN;


/**
	Flags of TCP
*/
uint8_t ack = 0;
uint8_t syn = 0;
uint8_t fin = 0;

/**
	TCP fields cache
*/
extern uint8_t last_seq;
extern uint8_t last_ack;
extern uint8_t last_len;
uint8_t last_route_len;

extern uint8_t msgOK;
extern uint8_t msgInProgress;

extern uint8_t *last_msg;

uint8_t done = 0;

uint8_t reroute[100];



/**
	This function forces the state machine to CLOSED / LISTEN
	It can be used in case of timeout or error

	@brief Close state forcing
*/
void force_close_est() {

	if (state != ESTABLISHED) {
		state = CLOSED_LISTEN;
		DEBUGM("CLOSED_LISTEN forced\n");
	}

return;
}

/**
	This function forces the state machine to CLOSED / LISTEN
	It can be used in case of timeout or error

	@brief Close state forcing
*/
void force_close_close() {

	state = CLOSED_LISTEN;

return;
}


/**
	Process a message in the TCP layer, this imlpements the state machine
	It can be used to send or recieve a message

d es el mensaje
thisNode el nodo actual
mode = 0 enviar
mode = 1 recibir
*/
void tcp_process(uint8_t *d, uint8_t msgLen, uint8_t routeLen, uint8_t *route, uint8_t thisNode, uint8_t mode) {
uint8_t *m = NULL;
uint8_t tcp_m[4], msg_tcp[200], msg_send[200];
uint8_t i = 0;


	/*Send a message when a connection is in ESTABLISHED*/
	if (mode == 0){


		//for (i=0;i<routeLen;i++) printf("RR: %d\n", route[i]);

		/*Make message in the TCP layer*/
		msg_tcp[POS_SEQ] = last_seq;
		msg_tcp[POS_ACK] = last_ack;
		msg_tcp[POS_FLAGS] = 0;
		msg_tcp[POS_TCP_PAYLOADLEN] = msgLen;
		last_len = msgLen;
		for (i=0; i<msgLen; i++) msg_tcp[POS_TCP_PAYLOAD+i] = d[i];

		/*If we get a route ok*/
		if (routeLen != 0)
			msg_make(msg_send, route, routeLen, msg_tcp, msgLen+4, 0);
		/*If not we go with the last one*/
		else
			msg_make(msg_send, reroute, last_route_len, msg_tcp, msgLen+4, 0);

		msg_process(msg_send, thisNode);

		//printf("Msg sent:\nseq=%d\nack=%d\nlen=%d\n", last_seq, last_ack, msgLen);


	}

	/*First of all, let's see if there is a message for us (end to end communication)
	 If not, the msg_process function will bounce it*/
	else if (msg_process(d, thisNode) && mode == 1) {

		/*Extract the message*/
		m = msg_get_message(d);

		/*Set the flags*/
		ack = ((m[POS_FLAGS] & F_ACK) == F_ACK);
		syn = ((m[POS_FLAGS] & F_SYN) == F_SYN);
		fin = ((m[POS_FLAGS] & F_FIN) == F_FIN);

		/*Build the route back from the route receieved*/
		for (i=0; i<routeLen; i++){
			reroute[i] = route[routeLen-i-1];
			printf("R: %d\n", reroute[i]);
		}
		last_route_len = routeLen;

		DEBUGM("state=%d ack=%d, syn=%d, fin=%d\n", state, ack, syn, fin);

		for (i=0; i<msgLen; i++) printf("MSG PAYLOAD: [%d] : %X\n", i, m[i]);

		/*State machine*/
		switch (state) {
		
	
			/*LISTEN state in fact for receiving*/
			case (CLOSED_LISTEN) :		/*Trying to establish a connection*/
							if (!ack && syn && !fin) {

								/*Reply with SYN+ACK and ack=seq+1*/
								tcp_m[POS_SEQ] = 0;
								tcp_m[POS_ACK] = m[POS_SEQ] + 1;
								tcp_m[POS_FLAGS] = F_ACK + F_SYN;
								tcp_m[POS_TCP_PAYLOADLEN] = 0;

								msg_make(msg_tcp, reroute, routeLen, tcp_m, 4, 0);
								msg_process(msg_tcp, thisNode);

								/*Go to next state*/
								state = SYN_RECV;

								printf("LISTEN -> SYN_RECV\n");

								/*If we are not in ESTABLISHED in N secs -> CLOSED*/
								Timers_add(TCP_TIMEOUT, force_close_est, 0xFFFF);
							
							}
							break;

			
			/*Just wait the final ACK and the connection is done*/
			case (SYN_RECV) :		if (ack && !syn && !fin) {

								/*Go to next state*/
								last_seq = m[POS_SEQ];
								last_ack = m[POS_ACK];
								state = ESTABLISHED;

								printf("SYN_RECV -> ESTABLISHED\n");
							
							}
							break;
		
			
			/*SYN_SENT now we have to receive SYN+ACK and reply with ACK*/
			case (SYN_SENT) :		
							if (ack && syn && !fin) {

								/*Reply with ACK and ack=seq+1*/
								tcp_m[POS_SEQ] = 0;
								tcp_m[POS_ACK] = m[POS_SEQ] + 1;
								tcp_m[POS_FLAGS] = F_ACK;
								tcp_m[POS_TCP_PAYLOADLEN] = 0;

								msg_make(msg_tcp, reroute, routeLen, tcp_m, 4, 0);
								msg_process(msg_tcp, thisNode);

								/*Go to next state*/
								last_seq = m[POS_SEQ];
								last_ack = m[POS_ACK];
								state = ESTABLISHED;
								
								printf("SYN_SENT -> ESTABLISHED\n");
							
							}
							break;


			/*A regular message or a termination*/
			case (ESTABLISHED) :
							/*Connection termination*/
							if (!ack && !syn && fin) {

								/*Reply with ACK and ack=seq+1*/
								tcp_m[POS_SEQ] = 0;
								tcp_m[POS_ACK] = m[POS_SEQ] + 1;
								tcp_m[POS_FLAGS] = F_ACK;
								tcp_m[POS_TCP_PAYLOADLEN] = 0;

								msg_make(msg_tcp, reroute, routeLen, tcp_m, 4, 0);
								msg_process(msg_tcp, thisNode);

								/*Go to next state*/
								state = CLOSE_WAIT;

								printf("ESTABLISHED -> CLOSE_WAIT\n");

								
								/*Wait a moment... passive closing*/
								/*wait(5000);*/

								/*Reply with FIN and ack=seq+1*/
								tcp_m[POS_SEQ] = 0;
								tcp_m[POS_ACK] = m[POS_SEQ] + 1;
								tcp_m[POS_FLAGS] = F_FIN;
								tcp_m[POS_TCP_PAYLOADLEN] = 0;

								msg_make(msg_tcp, reroute, routeLen, tcp_m, 4, 0);
								msg_process(msg_tcp, thisNode);

								/*Go to next state*/
								state = LAST_ACK;

								printf("CLOSE_WAIT -> LAST_ACK\n");

								/*If we are not in CLOSED in N secs -> CLOSED*/
								Timers_add(TCP_TIMEOUT, force_close_close, 0xFFFF);


							}

							/*Message confirmation*/
							else if (ack && !syn && !fin) {

								printf("Msg ACK:\nR seq=%d, R ack=%d\nL seq=%d, L ack=%d\nL len=%d\n",\
								m[POS_SEQ], m[POS_ACK], last_seq, last_ack, last_len);

								/*If the confirmation is right, perfect, we mark as answered*/
								if (m[POS_SEQ] == last_ack && m[POS_ACK] == last_seq + last_len) {
									msgOK = 1;
									msgInProgress = 0;
									printf("MSG OK\n");
								}

								/*If not, we resend*/
								else {
									/*Make message in the TCP layer*/
									msg_tcp[POS_SEQ] = last_seq;
									msg_tcp[POS_ACK] = last_ack;
									msg_tcp[POS_FLAGS] = 0;
									msg_tcp[POS_TCP_PAYLOADLEN] = last_len;
									for (i=0; i<last_len; i++) msg_tcp[POS_TCP_PAYLOAD+i] = last_msg[i];
							
									msg_make(msg_send, reroute, routeLen, msg_tcp, last_len+4, 0);
									msg_process(msg_send, thisNode);	
								}

								/*Update the fields values !*/
								last_ack = m[POS_SEQ];
								last_seq = m[POS_ACK];
							}

							/*A connection wants to be established, we take it*/
							else if (!ack && syn && !fin) {

								/*Reply with SYN+ACK and ack=seq+1*/
								tcp_m[POS_SEQ] = 0;
								tcp_m[POS_ACK] = m[POS_SEQ] + 1;
								tcp_m[POS_FLAGS] = F_ACK + F_SYN;
								tcp_m[POS_TCP_PAYLOADLEN] = 0;

								msg_make(msg_tcp, reroute, routeLen, tcp_m, 4, 0);
								msg_process(msg_tcp, thisNode);
							}

	
							else {
								printf("Regular message !\n");
								printf("seq = %d\nack = %d\nlen = %d\n", m[POS_SEQ], m[POS_ACK], m[POS_TCP_PAYLOADLEN]);
								printf("ACK=%d\nSYN=%d\nFIN=%d\n", ack, syn, fin);
	
								/*Reply with seq = (ack) and ack = (seq + len) and ACK*/
								//last_seq = m[POS_SEQ];
								//last_ack = m[POS_ACK];
								tcp_m[POS_SEQ] = m[POS_ACK];
								tcp_m[POS_ACK] = m[POS_SEQ] + m[POS_TCP_PAYLOADLEN];
								tcp_m[POS_FLAGS] = F_ACK;
								tcp_m[POS_TCP_PAYLOADLEN] = 0;
	
								msg_make(msg_tcp, reroute, routeLen, tcp_m, 4, 0);
								msg_process(msg_tcp, thisNode);
							}


							break;


			/*Wait for the last confirmation of closing */
			case (LAST_ACK) :		
							if (ack && !syn && !fin) {

								/*The end :(*/
								state = CLOSED_LISTEN;

								printf("LAST_ACK -> CLOSED\n");

							}
							break;


			/*FIN_WAIT1, confirm this with an ACK*/
			case (FIN_WAIT1) :
							if (ack && !syn && !fin) {

								/*Go to next state*/
								state = FIN_WAIT2;

								printf("FIN_WAIT1 -> FIN_WAIT2\n");
							
								/*If we are not in CLOSED in N secs -> CLOSED*/
								Timers_add(TCP_TIMEOUT, force_close_close, 0xFFFF);
							}
							break;

			/*FIN_WAIT2, recv a FIN and confirm the end with an ACK*/
			case (FIN_WAIT2) :
							if (!ack && !syn && fin) {

								/*Reply with ACK and ack=seq+1*/
								tcp_m[POS_SEQ] = 0;
								tcp_m[POS_ACK] = m[POS_SEQ] + 1;
								tcp_m[POS_FLAGS] = F_ACK;
								tcp_m[POS_TCP_PAYLOADLEN] = 0;

								msg_make(msg_tcp, reroute, routeLen, tcp_m, 4, 0);
								msg_process(msg_tcp, thisNode);

								/*Go to next state*/
								state = CLOSED_LISTEN;

								printf("FIN_WAIT2 -> CLOSED\n");
							
							}
							break;

		}
		
	}
	
}


/*Launches the establishment operation*/
void tcp_connect(uint8_t *d, uint8_t *route, uint8_t thisNode) {
uint8_t tcp_m[4];

	/*
	m[0] = seq
	m[1] = ack
	m[2] = flags
	m[3] = payload_len
	*/
	tcp_m[POS_SEQ] = 0;
	tcp_m[POS_ACK] = 0;
	tcp_m[POS_FLAGS] = F_SYN;
	tcp_m[POS_TCP_PAYLOADLEN] = 0;

	/*Build the SYN message and send it*/
	msg_make(d, route+1, route[0], tcp_m, 4, 0);
	msg_process(d, thisNode);

	/*Next state, we force it no matter what state the connection is in*/
	state = SYN_SENT;

	printf("(%d) -> SYN_SENT\n", state);

	/*If we are not in ESTABLISHED in N secs -> CLOSED*/
	//Timers_add(TCP_TIMEOUT, force_close_est, 0xFFFF);


}


/*Launches the termination of a connection*/
void tcp_close(uint8_t *d, uint8_t *route, uint8_t thisNode) {
uint8_t tcp_m[4];

	/*
	m[0] = seq
	m[1] = ack
	m[2] = flags
	m[3] = payload_len
	*/
	tcp_m[POS_SEQ] = 0;
	tcp_m[POS_ACK] = 0;
	tcp_m[POS_FLAGS] = F_FIN;
	tcp_m[POS_TCP_PAYLOADLEN] = 0;

	/*Build the FIN message and send it*/
	msg_make(d, route+1, route[0], tcp_m, 4, 0);
	msg_process(d, thisNode);

	/*Next state, we force it no matter what state the connection is in*/
	state = FIN_WAIT1;

	printf("(%d) -> FIN_WAIT1\n", state);

	/*If we are not in CLOSED in N secs -> CLOSED*/
	Timers_add(TCP_TIMEOUT, force_close_close, 0xFFFF);
}


