/*
* tcp port
*/
#include "./include/tcp_port.h"
#include "./include/tcp_if.h"

#define TCP_F_BB1                             0x7E          // Begin: The first byte
#define TCP_F_BB2                             0xA5          // Begin: The second byte
#define TCP_F_EB1                             0x7E          // End: The first byte
#define TCP_F_EB2                             0x0D          // End: The second byte

#define TCP_F_INF_BEGIN_FLAG_LEN              2             // length of begin flag
#define TCP_F_INF_MSG_LEN_LEN                    2           // length of msg length field
//#define TCP_F_INF_MSG_TYPE_LEN                2             // length of msg type
#define TCP_F_INF_END_FLAG_LEN                2             // length of end flag
#define TCP_F_INF_HEAD_LEN                        4            // length of format begin+lengthField
#define TCP_F_INF_ALL_LEN                          6             // total length of format

#define TCP_IsfBegin(f1,f2)    ((f1==TCP_F_BB1) && (f2==TCP_F_BB2))// Begin flag of msg
#define TCP_IsfEnd(f1,f2)      ((f1==TCP_F_EB1) && (f2==TCP_F_EB2))// End flag of msg

#define TCP_MSG_DATA                         1          // Data
#define TCP_MSG_HB                             2          // HB
#define TCP_MSG_HB_ACK                    3          // HB_ACK

#define TCP_TIMER_1_SEC                      (100)      // 10ms * 100
#define TCP_TIMER_WAIT_CONNECT      (5)	 // 50ms
#define TCP_TIMER_REINIT	             (3*TCP_TIMER_1_SEC)      // 3 Sec
#define TCP_TIMER_RECONNECT             (3*TCP_TIMER_1_SEC)      // 3 Sec
#define TCP_TIMER_HB             (3*TCP_TIMER_1_SEC)      // 3 Sec

#define TCP_MAX_RETRY_NUM      (3)

typedef enum
{
	TCP_STATUS_NULL,
	TCP_STATUS_INITING,
	TCP_STATUS_INITED,
	TCP_STATUS_CONNECTING,
	TCP_STATUS_TESTING,
	TCP_STATUS_CONNECTED,
	TCP_STATUS_MAX,
}e_tcp_status;

typedef struct
{
	WORD         usMsgId;             // Tcp internal message Id, 1: Data; 2: HB; 3: HB_ACK
	WORD         usPlLen;              // Payload length
	DWORD       param1;           // App param1, optional
	DWORD       param2;          // App param2, optional
	DWORD       param3;          // App param3, optional
} tcp_head_t;

static char tcp_status_str[TCP_STATUS_MAX][64] = {\
	"null", \
	"initing", \
	"inited", \
	"connecting", \
	"testing", \
	"connected"
};

static tcp_port_t tcp_port[TCP_MAX_NUM_OF_PORT];

void tcp_port_init(WORD id)
{
	tcp_port_t *ptr_port = NULL;

	if(id >= TCP_MAX_NUM_OF_PORT)
	{
		return;
	}
	
	ptr_port = &tcp_port[id];
	memset((BYTE*)ptr_port, 0, sizeof(tcp_port_t));
	ptr_port->fd = -1;
	ptr_port->cli_fd = -1;
	ptr_port->ptr_sap = NULL;

	return;
}

long tcp_str_to_uip(char *src_ip)
{
	return inet_addr(src_ip);
}

static tcp_port_t *tcp_port_get_by_index(WORD index)
{
	if (index >= TCP_MAX_NUM_OF_PORT)
	{
		return NULL;
	}

	return &tcp_port[index];
}


static int tcp_port_get_unused_pid(void)
{
	static WORD index = 0;
	WORD id, i;
	tcp_port_t *ptr_port = NULL;

	i = index;
	do
	{
		id = i;
		ptr_port = &tcp_port[id];
		if(++i >= TCP_MAX_NUM_OF_PORT)
		{
			i = 0;
		}
		
		if(ptr_port->flag != FALSE)
		{
			continue;
		}
		
		index = i;
		return id;
	}while(i != index);

	return -1;
}


static int tcp_init_socket(tcp_session_t *pSess)
{
	int sock = -1, on = 1, timeout = 20;
	struct sockaddr_in srcaddr;
	
	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		return -1;
	}
	
	if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
	{
		close(sock);
		return -2;
	}

	//if (bServer)
	{
		if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &timeout, sizeof(timeout)) != 0)
		{
			return -3;
		}
	}
	
	if (fcntl(sock, F_SETFL, O_NONBLOCK) < 0)
	{
		close(sock);
		return -4;
	}
	
	memset( (void *)&srcaddr, 0, sizeof(srcaddr) );
	srcaddr.sin_family = AF_INET;
	srcaddr.sin_addr.s_addr = pSess->src_ip;//htonl( INADDR_ANY );
	srcaddr.sin_port = htons(pSess->src_port);
	if (bind(sock, (struct sockaddr *)&srcaddr, sizeof(srcaddr)) < 0)
	{
		close(sock);
		return -5;
	}

	if (pSess->bServer)
	{
		// change to passive socket
		if (listen(sock, 5))
		{
			close(sock);
			return -6;
		}
	}

	return sock;
}


static int tcp_set_status(tcp_port_t *ptr_port, WORD new_status)
{
	if ((ptr_port == NULL) || (ptr_port->status == new_status) || (new_status >= TCP_STATUS_MAX))
	{
		return -1;
	}


	if (ptr_port->status == TCP_STATUS_CONNECTED)
	{
		ptr_port->disconnect_num++;
	}
	
	ptr_port->status = new_status;

	return 0;
}


static int tcp_connect(tcp_port_t *ptr_port)
{
	int ret;
	struct sockaddr_in serv_addr;

	if (ptr_port == NULL)
	{
		return -1;
	}

	/* set family to internet */
	bzero((char *) &serv_addr, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = ptr_port->sess.dst_ip;
	serv_addr.sin_port = htons(ptr_port->sess.dst_port);
	ret = connect(ptr_port->fd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr));
	if (ret < 0)
	{
		if (errno == EINPROGRESS)
		{
			return 0;
		}
		
		/*if((106 == errno)//Transport endpoint is already connected
			||(89 == errno))//Destination address required
		{
			return 0;
		}*/
		
		return -1;
	}
	else
	{
		return 0;
	}
}


static int tcp_check_conn(tcp_port_t *ptr_port)
{
	int client_sock;
	int timeout = 20;
	struct sockaddr_in remote;
	socklen_t len = sizeof(struct sockaddr_in);
	tcp_session_t *pSess = NULL;

	if (ptr_port == NULL)
	{
		return -1;
	}
	pSess = &ptr_port->sess;
	
	// fill with zero terms
	memset(&remote, 0, sizeof( remote));

	// wait for connetion
	client_sock = accept(ptr_port->fd, (struct sockaddr *)&(remote), &len);
	if ( client_sock < 0 )
	{
		return -1;
	}

	if ((remote.sin_port != htons(pSess->dst_port)) || (remote.sin_addr.s_addr != pSess->dst_ip))
	{
		close(client_sock);
		return -2;
	}

	if (setsockopt(client_sock, SOL_SOCKET, SO_KEEPALIVE, &timeout, sizeof(timeout)) != 0)
	{
		//return -3;
	}

	if (fcntl(client_sock, F_SETFL, O_NONBLOCK) < 0)
	{
		//return -4;
	}

	ptr_port->cli_fd = client_sock;
	return client_sock;
}


int tcp_port_alloc_port(int sap_id, WORD usr_port, tcp_session_t *pSess)
{
	int			pid;
	tcp_port_t	*ptr_port = NULL;
	tcp_sap_t	*ptr_sap = NULL;

	if(pSess == NULL || pSess->src_ip == 0 || pSess->src_port == 0 \
		|| pSess->dst_ip == 0 || pSess->dst_port == 0)
	{
		return -1;
	}
	
	if((ptr_sap = tcp_sap_get_by_index(sap_id)) == NULL)
	{
		return -1;
	}
	
	if((pid = tcp_port_get_unused_pid()) < 0)
	{
		return -1;
	}
	
	ptr_port = &tcp_port[pid];
	if((ptr_port->fd = tcp_init_socket(pSess)) < 0)
	{
		return -1;
	}

	ptr_port->flag = TRUE;
	ptr_port->index = pid;
	tcp_set_status(ptr_port, TCP_STATUS_INITED);
	ptr_port->timer = 5;
	ptr_port->retry_num = 0;
	ptr_port->ptr_sap = ptr_sap;
	ptr_port->usr_port = usr_port;
	ptr_port->cli_fd = -1;
	ptr_port->buf_len = 0;

	ptr_port->connected_num = 0;
	ptr_port->disconnect_num = 0;
	ptr_port->sent_fail_num = 0;

	memcpy(&ptr_port->sess, pSess, sizeof(tcp_session_t));

	return pid;
}


int tcp_port_release_port(int sap_id, WORD usr_port, WORD pid)
{
	tcp_sap_t *ptr_sap = NULL;
	tcp_port_t *ptr_port = NULL;
	
	if((ptr_sap = tcp_sap_get_by_index(sap_id)) == NULL)
	{
		return -1;
	}
	
	if((ptr_port = tcp_port_get_by_index(pid)) == NULL)
	{
		return -1;
	}
	
	if((ptr_port->ptr_sap != ptr_sap) || (ptr_port->usr_port != usr_port))
	{
		return -1;
	}
	
	if(ptr_port->fd >= 0)
	{
		close(ptr_port->fd);
	}
	
	tcp_port_init(pid);

	return 0;
}

tcp_session_t *tcp_port_update_sess(int sap_id, WORD usr_port, WORD pid, tcp_session_t *ptr_sess)
{
	tcp_sap_t *ptr_sap = NULL;
	tcp_port_t	*ptr_port = NULL;

	if(ptr_sess == NULL)
	{
		return NULL;
	}
	
	if((ptr_sap = tcp_sap_get_by_index(sap_id)) == NULL)
	{
		return NULL;
	}
	
	if((ptr_port = tcp_port_get_by_index(pid)) == NULL)
	{
		return NULL;
	}
	
	if((ptr_port->ptr_sap != ptr_sap) || (ptr_port->usr_port != usr_port))
	{
		return NULL;
	}
	
	memcpy((BYTE*)&ptr_port->sess, (BYTE*)ptr_sess, sizeof(tcp_session_t));

	return &ptr_port->sess;
}


int get_tcp_sess(WORD pid, tcp_session_t *sess)
{
	tcp_port_t	*ptr_port = NULL;

	if (pid >= TCP_MAX_NUM_OF_PORT)
	{
		return -1;
        }
	
	if(sess == NULL)
	{
		return -1;
	}

	if((ptr_port = tcp_port_get_by_index(pid)) == NULL)
	{
		return -1;
	}

	memcpy(sess, &ptr_port->sess, sizeof(tcp_session_t));

	return 0;
}


static int tcp_send_msg(tcp_port_t *ptr_port, BYTE *msg, WORD len)
{
	int		fd;

	if((ptr_port == NULL) || (len > TCP_MAX_SEND_MSG_LEN))
	{
		return -1;
	}
	
	if (ptr_port->sess.bServer)
	{
		fd = ptr_port->cli_fd;
	}
	else
	{
		fd = ptr_port->fd;
	}

	if (fd < 0)
	{
		return -1;
	}

	if (send(fd, msg, len, MSG_NOSIGNAL) < 0)// add MSG_NOSIGNAL, to avoid Broken pipe, while sending data after received FIN and sent FIN_ACK
	{
		return -1;
	}
	else
	{
	}

	return 0;
}


static int tcp_send_hb(tcp_port_t *ptr_port, WORD msg_id)
{
	int	ret = -1;
	BYTE	msg_buf[TCP_MAX_SEND_MSG_LEN];
	WORD	msg_len;
	DWORD       pos = 0;
	BYTE            *pDst = NULL;
	tcp_head_t   *pHead = NULL;
	
	if(ptr_port == NULL || ptr_port->flag == FALSE)
	{
		return -1;
	}
	
	pDst = msg_buf;

	// Begin flag
	pDst[pos++] = TCP_F_BB1;
	pDst[pos++] = TCP_F_BB2;
	//

	// len, 2 Bytes
	msg_len = htons(sizeof(tcp_head_t));
	memcpy(&pDst[pos], (BYTE *)&msg_len, sizeof(msg_len));
	pos += sizeof(msg_len);
	//

        // Head
        pHead = (tcp_head_t *)&pDst[pos];
        pHead->usMsgId = htons(msg_id);
        pHead->usPlLen = 0;
        pHead->param1 = 0;
        pHead->param2 = 0;
        pHead->param3 = 0;
        memcpy(&pDst[pos], (BYTE *)pHead, sizeof(tcp_head_t));
        pos += sizeof(tcp_head_t);
        //

        // flagEnd
        pDst[pos++] = TCP_F_EB1;
        pDst[pos++] = TCP_F_EB2;
        //

	msg_len = pos;

	ret = tcp_send_msg(ptr_port, msg_buf, msg_len);
	
	return ret;
}


static int tcp_handle_hb(tcp_port_t *ptr_port, tcp_head_t *pTcpHead)
{
	WORD usMsgId = 0;
	if (ptr_port == NULL || pTcpHead == NULL)
	{
		return -1;
	}
	
	if (pTcpHead->usPlLen != 0)
	{
		return -1;
	}

	usMsgId = ntohs(pTcpHead->usMsgId);
	if (usMsgId == TCP_MSG_HB)
	{
		tcp_send_hb(ptr_port, TCP_MSG_HB_ACK);
	}
	else if (usMsgId == TCP_MSG_HB_ACK)
	{
		if (ptr_port->status == TCP_STATUS_TESTING)
		{
			tcp_set_status(ptr_port, TCP_STATUS_CONNECTED);
			ptr_port->connected_num++;
			ptr_port->retry_num = 0;
		}
		else if (ptr_port->status == TCP_STATUS_CONNECTED)
		{
			ptr_port->retry_num = 0;
		}
	}

	return 0;
}


//static int tcp_port_handle_recv_msg(WORD pid, BYTE *buf, WORD len)
static int tcp_port_handle_recv_msg(tcp_port_t *ptr_port)
{
	tcp_sap_t		*ptr_sap = NULL;

	if(ptr_port == NULL || ptr_port->ptr_sap == NULL)
	{
		return -1;
	}
	
	ptr_sap = ptr_port->ptr_sap;
	if(ptr_sap->recv_cb == NULL)
	{
		return -1;
	}

	ptr_sap->recv_cb(ptr_port->usr_port, ptr_port->rv_buf, ptr_port->buf_len);

	return 0;
}

void tcp_port_recv_msg(void)
{
	int index, i, len, addr_len, fd, pos;
	BYTE msg_buf[TCP_MAX_RECV_MSG_LEN];
	//struct timeval tv;

	struct sockaddr_in	sin_addr;
	tcp_port_t *ptr_port = NULL;
	
	addr_len = sizeof(struct sockaddr);
	memset(&sin_addr, 0, addr_len);
	
	for(index = 0; index < TCP_MAX_NUM_OF_PORT; index++)
	{
		ptr_port = &tcp_port[index];
		if(ptr_port->flag == FALSE)
		{
			continue;
		}
		
		if(((ptr_port->sess.bServer) && (ptr_port->fd < 0 || ptr_port->cli_fd < 0)) \
			|| ((!ptr_port->sess.bServer) && (ptr_port->fd < 0)))
		{
			continue;
		}

		if (ptr_port->sess.bServer)
		{
			fd = ptr_port->cli_fd;
		}
		else
		{
			fd = ptr_port->fd;
		}
		
		for(i = 0; i < TCP_MAX_RECV_COUNT; i++)
		{
			pos = ptr_port->buf_len;
			len = recv(fd, msg_buf, TCP_MAX_RECV_MSG_LEN, 0);
			if(len <= 0)
			{
				break;
			}
			
			if((pos + len) >= TCP_MAX_RECV_BUF_LEN)
			{
				ptr_port->buf_len = 0;
				pos = 0;
				break;
			}

			memcpy(&ptr_port->rv_buf[pos], msg_buf, len);
			ptr_port->buf_len += len;
			tcp_port_handle_recv_msg(ptr_port);
		}
	}

	return;
}


int tcp_send(int sap_id, WORD usr_port, WORD pid, BYTE *payload, WORD len)
{
	int	ret = -1;
	BYTE	msg_buf[TCP_MAX_SEND_MSG_LEN];
	WORD	msg_len;
	tcp_sap_t		*ptr_sap = NULL;
	tcp_port_t	*ptr_port = NULL;
	WORD       pos = 0;
	BYTE            *pDst = NULL;
	
	if((payload == NULL) || (len > TCP_MAX_PAYLOAD_SIZE))
	{
		return -1;
	}
	
	if((ptr_sap = tcp_sap_get_by_index(sap_id)) == NULL)
	{
		return -1;
	}
	
	if(pid >= TCP_MAX_NUM_OF_PORT)
	{
		return -1;
	}
	
	ptr_port = &tcp_port[pid];
	if((ptr_port->flag == FALSE) || (ptr_port->ptr_sap != ptr_sap) || (ptr_port->usr_port != usr_port))
	{
		return -1;
	}
/*
	if (ptr_port->status != TCP_STATUS_CONNECTED)
	{
		return -1;
	}
*/	
	pDst = msg_buf;

        memcpy(&pDst[pos], payload, len);
        pos += len;
  
	  msg_len = pos;

	ret = tcp_send_msg(ptr_port, msg_buf, msg_len);
	if(ret < 0)
	{
		ptr_port->sent_fail_num++;
	}
	
	return ret;
}


int tcp_link_fsm()
{
	int i;
	tcp_port_t *ptr_port = NULL;
	tcp_session_t *pSess = NULL;

	for (i=0; i<TCP_MAX_NUM_OF_PORT; i++)
	{
		ptr_port = &tcp_port[i];

		if (!ptr_port->flag)
		{
			continue;
		}

		//if (ptr_port->fd < 0)
		//{
		//	tcp_port_init(i);
		//	continue;
		//}

		pSess = &ptr_port->sess;
		switch (ptr_port->status)
		{
		case TCP_STATUS_INITING:
			if (--ptr_port->timer > 0)
			{
				continue;
			}

			ptr_port->fd = tcp_init_socket(pSess);
			if (ptr_port->fd < 0)
			{
				ptr_port->timer = TCP_TIMER_REINIT;
			}
			else
			{
				tcp_set_status(ptr_port, TCP_STATUS_INITED);
			}
			break;
		case TCP_STATUS_INITED:
			if (--ptr_port->timer > 0)
			{
				continue;
			}
			
			if (pSess->bServer)
			{
				if (tcp_check_conn(ptr_port) < 0)
				{
					ptr_port->timer = TCP_TIMER_WAIT_CONNECT;
				}
				else
				{
					ptr_port->timer = 0;
					ptr_port->retry_num = 0;
					tcp_set_status(ptr_port, TCP_STATUS_TESTING);
				}
			}
			else
			{
				if (tcp_connect(ptr_port) < 0)
				{
					close(ptr_port->fd);
					ptr_port->fd = -1;
					tcp_set_status(ptr_port, TCP_STATUS_INITING);
					ptr_port->timer = TCP_TIMER_REINIT;
				}
				else
				{
					ptr_port->timer = TCP_TIMER_WAIT_CONNECT;
					ptr_port->retry_num = 0;
					tcp_set_status(ptr_port, TCP_STATUS_TESTING);
				}
			}
			break;
		case TCP_STATUS_TESTING:// sending HB
		case TCP_STATUS_CONNECTED:
			break;
			if (--ptr_port->timer > 0)
			{
				continue;
			}

			if (ptr_port->retry_num++ >= TCP_MAX_RETRY_NUM)
			{
				if (ptr_port->sess.bServer)
				{
					close(ptr_port->cli_fd);
					ptr_port->cli_fd = -1;
					ptr_port->timer = 0;
					tcp_set_status(ptr_port, TCP_STATUS_INITED);
				}
				else
				{
					close(ptr_port->fd);
					ptr_port->fd = -1;
					ptr_port->timer = 0;
					tcp_set_status(ptr_port, TCP_STATUS_INITING);
				}
			}
			else
			{
				//tcp_send_hb(ptr_port, TCP_MSG_HB);
				ptr_port->timer = TCP_TIMER_HB;
			}
			break;
		default:
			break;
		}
	}

	return 0;
}


BOOL tcp_print_all_port(void)
{
	int j = 0, i;
	char buf[516], tmp_buf[8];
	tcp_port_t	*ptr_port = NULL;

	strcpy(buf, "\0");
	for(i = 0; i < TCP_MAX_NUM_OF_PORT; i++)
	{
		ptr_port = &tcp_port[i];
		if(ptr_port->flag == TRUE)
		{
			if(j % 20 == 0 && j != 0)
			{
				strcpy(buf, "\0");
			}
			sprintf(tmp_buf, "%d ", i);
			strcat(buf, tmp_buf);
			j++;
		}
	}
	
	if(j % 20)
	{
	}

	return TRUE;
}

BOOL tcp_print_port(int pid)
{
	tcp_port_t	*ptr_port = NULL;

	if((pid < 0) || (pid >= TCP_MAX_NUM_OF_PORT))
	{
		return FALSE;
	}
	
	ptr_port = &tcp_port[pid];
	if(ptr_port->flag == FALSE)
	{
		return TRUE;
	}
	
	if (ptr_port->status < TCP_STATUS_MAX)
	{
	}
	if (ptr_port->sess.bServer)
	{
	}

	return TRUE;
}


BOOL tcp_clear_stat(int pid)
{
	tcp_port_t	*ptr_port = NULL;

	if((pid < 0) || (pid >= TCP_MAX_NUM_OF_PORT))
	{
		return FALSE;
	}
	
	ptr_port = &tcp_port[pid];
	if(ptr_port->flag == FALSE)
	{
		return TRUE;
	}
	
	ptr_port->connected_num = 0;
	ptr_port->disconnect_num = 0;
	ptr_port->sent_fail_num = 0;

	return TRUE;
}

