#ifdef __KERNEL__
#include <linux/module.h>
#include <linux/skbuff.h>
#endif

#include <rtk_rg_liteRomeDriver.h>
#include <rtk_rg_ftp.h>
#include <rtk_rg_alg_tool.h>


int _rtk_rg_ftp_parse_addressAndPortString(unsigned char * pData, ipaddr_t * pIpAddr, unsigned short * pPortNum)
{
	char *endCP;
	
#ifdef __KERNEL__
	*pIpAddr = simple_strtoul(pData,&endCP,0);

	*pIpAddr<<=8;

	pData=endCP+1;
	*pIpAddr += simple_strtoul(pData,&endCP,0);

	*pIpAddr<<=8;

	pData=endCP+1;
	*pIpAddr += simple_strtoul(pData,&endCP,0);

	*pIpAddr<<=8;

	pData=endCP+1;
	*pIpAddr += simple_strtoul(pData,&endCP,0);

	pData=endCP+1;
	*pPortNum = simple_strtoul(pData,&endCP,0);

	*pPortNum<<=8;

	pData=endCP+1;
	*pPortNum += simple_strtoul(pData,&endCP,0);
#endif

	return 1;
}

static char portString[256]={0};
static char preStr[64]={0};
static char postStr[64]={0};

static int _rtk_rg_ftp_process(int direct, unsigned char * pData, unsigned int dataLen,rtk_rg_alg_connection_t * pConn)
{
	int ret,newDelta=0;
	ipaddr_t ipAddr,gwIPAddr;
	unsigned short portNum,newPort;
	unsigned int newLen;
	rtk_rg_pktHdr_t *pPktHdr;
	struct sk_buff *skb;
	rtk_rg_upnpConnection_t upnpConn;
	
	pPktHdr = (rtk_rg_pktHdr_t *)pConn->pPktHdr;
	skb = (struct sk_buff *)pConn->skb;

	if(direct==NAPT_DIRECTION_OUTBOUND)
	{
		//Server In WAN's ACTIVE MODE
		if(*((unsigned int *)pData) == htonl(ALG_FTP_PORT_STR))
		{
			//DEBUG("len is %d, l4offset is %d, headerlen is %d",dataLen,pPktInfo->l4Offset, pPktInfo->headerLen);
			//DEBUG("POST_FUNCTION outbound: i am in _rtk_rg_algRegFunc_TCP_FTP!! data len is %d dport is %d",dataLen,pPktInfo->dport);
			//DEBUG("DATA is:");
			ipAddr=0;
			portNum=0;
			_rtk_rg_ftp_parse_addressAndPortString(pData+ALG_FTP_PORT_LENGTH,&ipAddr,&portNum);	//bypass string "PORT "

			newPort=_rtk_rg_extPort_get(1,portNum);
			if(newPort==FAIL) return SUCCESS;

			gwIPAddr=pConn->tuple.extIp.ip;
			snprintf(portString,sizeof(portString),"PORT %d,%d,%d,%d,%d,%d\r\n",
				(gwIPAddr>>24)&0xff,
				(gwIPAddr>>16)&0xff,
				(gwIPAddr>>8)&0xff,
				gwIPAddr&0xff,
				newPort>>8,
				newPort&0xff);

			//DEBUG("the modified port command is \"%s\"",portString);
			//DEBUG("data before len is %d, after is %d",dataLen,strlen(portString));

			newLen=strlen(portString);
			newDelta=(newLen-dataLen);
			//DEBUG("the newDelta is %d",newDelta);
			
			if(skb->tail+newDelta > skb->end)	
			{
				//need to re-alloc skb data structure
				FIXME("Overflow! we need to re-alloc skb data stucture...");
			}
			else
			{
				//copy the new string into skb and enlarge or shrink the length
				memcpy(pData,portString,newLen);

				//fix packet length
				//DEBUG("sip is %x, sport is %d",pPktInfo->ipv4Sip,pPktInfo->sport);					
				if(newDelta != 0)
				{
					skb->len += newDelta;
					skb->tail += newDelta;
					pPktHdr->l3Len += newDelta;
					*pPktHdr->pL3Len=htons(pPktHdr->l3Len);
					//20140507LUKE:if pppoe, we should add to pppoe len here!!
					if((pPktHdr->egressTagif&PPPOE_TAGIF)>0)
						*pPktHdr->pPppoeLength=htons(ntohs(*pPktHdr->pPppoeLength) + newDelta);

					//20140507LUKE:re-caculate IP checksum (because total len change)
					*pPktHdr->pIpv4Checksum=0;
					*pPktHdr->pIpv4Checksum=htons(inet_chksum(skb->data+pPktHdr->l3Offset,pPktHdr->l4Offset-pPktHdr->l3Offset));
				}
			}

			//20140507LUKE:re-caculate TCP checksum (because payload change)
			*pPktHdr->pL4Checksum=0;
			*pPktHdr->pL4Checksum=htons(inet_chksum_pseudo(skb->data+pPktHdr->l4Offset,pPktHdr->l3Offset+pPktHdr->l3Len-pPktHdr->l4Offset,ntohl(*pPktHdr->pIpv4Sip),ntohl(*pPktHdr->pIpv4Dip),pPktHdr->ipProtocol));

			pConn->outDelta+=newDelta;	//update connection's total delta
		
			//Add a one-shot UPnP flow for incoming connection to L4 WAN		
			upnpConn.is_tcp=pConn->tuple.isTcp;
			upnpConn.wan_intf_idx=pPktHdr->netifIdx;
			upnpConn.gateway_port=newPort;
			upnpConn.local_ip=pConn->tuple.internalIp.ip;
			upnpConn.local_port=portNum;
			upnpConn.limit_remote_ip=1;
			upnpConn.limit_remote_port=0;		//can't restrict client using which port to connect
			upnpConn.remote_ip=pConn->tuple.remoteIp.ip;
			upnpConn.remote_port=0;
			upnpConn.type=UPNP_TYPE_ONESHOT;
			upnpConn.timeout=rg_db.algUserDefinedTimeout[RTK_RG_ALG_FTP_TCP];	//auto time out if the server do not connect to this WAN
			assert_ok((pf.rtk_rg_upnpConnection_add)(&upnpConn,&ret));	
		}
		else if(*((unsigned int *)pData) == htonl(ALG_FTP_PASV_RESP_STR))		//Server In LAN's PASSIVE MODE
		{
			//DEBUG("len is %d, l4offset is %d, headerlen is %d",dataLen,pPktInfo->l4Offset, pPktInfo->headerLen);
			//DEBUG("POST_FUNCTION outbound: i am in _rtk_rg_algSrvInLanRegFunc_TCP_FTP!! data len is %d dport is %d",dataLen,pPktInfo->dport);
			//DEBUG("DATA is:");
			ipAddr=0;
			portNum=0;
			_rtk_rg_ftp_parse_addressAndPortString(strchr(pData,'(')+1,&ipAddr,&portNum); //bypass string before "("
			//DEBUG("the ipAddr is %x",ipAddr);
			//DEBUG("the port is %d",portNum);		

			newPort=_rtk_rg_extPort_get(1,portNum);
			if(newPort==FAIL) return SUCCESS;

			bzero(portString,sizeof(portString));
			bzero(preStr,sizeof(preStr));
			bzero(postStr,sizeof(postStr));
			
			gwIPAddr=pConn->tuple.extIp.ip;
			strncpy(preStr,pData,(unsigned char *)strchr(pData,'(')-pData);
			strncpy(postStr,strchr(pData,')')+1,(unsigned char *)strchr(pData,'\r')-(unsigned char *)strchr(pData,')')-1);
			//DEBUG("preStr is \"%s\", len is %d",preStr,(unsigned char *)strchr(pData,'(')-pData);
			//DEBUG("postStr is \"%s\", len is %d",postStr,(unsigned char *)strchr(pData,'\r')-(unsigned char *)strchr(pData,')')-1);
			snprintf(portString,sizeof(portString),"%s(%d,%d,%d,%d,%d,%d)%s\r\n",
				preStr,
				(gwIPAddr>>24)&0xff,
				(gwIPAddr>>16)&0xff,
				(gwIPAddr>>8)&0xff,
				gwIPAddr&0xff,		
				newPort>>8,
				newPort&0xff,
				postStr);
				

			//DEBUG("the modified port response is\"%s\"",portString);
			//DEBUG("data before len is %d, after is %d",dataLen,strlen(portString));

			newLen=strlen(portString);
			newDelta=(newLen-dataLen);
			//DEBUG("the newDelta is %d",newDelta);
			
			if(skb->tail+newDelta > skb->end)	
			{
				//need to re-alloc skb data structure
				FIXME("Overflow! we need to re-alloc skb data stucture...");
			}
			else
			{
				//copy the new string into skb and enlarge or shrink the length
				memcpy(pData,portString,newLen);

				//fix packet length
				//DEBUG("sip is %x, sport is %d",pPktInfo->ipv4Sip,pPktInfo->sport);					
				if(newDelta != 0)
				{
					skb->len += newDelta;
					skb->tail += newDelta;
					pPktHdr->l3Len += newDelta;
					*pPktHdr->pL3Len=htons(pPktHdr->l3Len);
					//20140507LUKE:if pppoe, we should add to pppoe len here!!
					if((pPktHdr->egressTagif&PPPOE_TAGIF)>0)
						*pPktHdr->pPppoeLength=htons(ntohs(*pPktHdr->pPppoeLength) + newDelta);
					
					//20140507LUKE:re-caculate IP checksum (because total len change)
					*pPktHdr->pIpv4Checksum=0;
					*pPktHdr->pIpv4Checksum=htons(inet_chksum(skb->data+pPktHdr->l3Offset,pPktHdr->l4Offset-pPktHdr->l3Offset));
				}
			}

			//20140507LUKE:re-caculate TCP checksum (because payload change)
			*pPktHdr->pL4Checksum=0;
			*pPktHdr->pL4Checksum=htons(inet_chksum_pseudo(skb->data+pPktHdr->l4Offset,pPktHdr->l3Offset+pPktHdr->l3Len-pPktHdr->l4Offset,ntohl(*pPktHdr->pIpv4Sip),ntohl(*pPktHdr->pIpv4Dip),pPktHdr->ipProtocol));

			pConn->outDelta+=newDelta;	//update connection's total delta
		
			//Add a one-shot UPnP flow for incoming connection to L4 WAN		
			upnpConn.is_tcp=pConn->tuple.isTcp;
			upnpConn.wan_intf_idx=pPktHdr->netifIdx;
			upnpConn.gateway_port=newPort;
			upnpConn.local_ip=pConn->tuple.internalIp.ip;
			upnpConn.local_port=portNum;
			upnpConn.limit_remote_ip=1;
			upnpConn.limit_remote_port=0;		//can't restrict client using which port to connect
			upnpConn.remote_ip=pConn->tuple.remoteIp.ip;
			upnpConn.remote_port=0;
			upnpConn.type=UPNP_TYPE_ONESHOT;
			upnpConn.timeout=rg_db.algUserDefinedTimeout[RTK_RG_ALG_FTP_TCP_SRV_IN_LAN];	//auto time out if the client do not connect to this WAN
			assert_ok((pf.rtk_rg_upnpConnection_add)(&upnpConn,&ret));	
			//TRACE("alg FTP upnp add success, newPort=%d  portNum=%d",newPort,portNum);
		}
	}
	return 1;
}

int _rtk_rg_ftp_handler(int direct, int after, unsigned char *pSkb,unsigned char *pPktInfo, unsigned char * pConnInfo)
{
	int ret = SUCCESS;
#ifdef __KERNEL__
	unsigned char * pData, *pAppData;
	unsigned int appLen=0,dataOff=0;
	rtk_rg_pktHdr_t *pPktHdr;
	struct sk_buff *skb;
	rtk_rg_alg_connection_t * pConn;
	
	pPktHdr = (rtk_rg_pktHdr_t *)pPktInfo;
	pConn = (rtk_rg_alg_connection_t *)pConnInfo;
	skb= (struct sk_buff *)pSkb;
	
	pData=skb->data;
	if(pPktHdr->tagif&TCP_TAGIF)
		dataOff = pPktHdr->l4Offset + pPktHdr->headerLen;
	else
		dataOff = pPktHdr->l4Offset + 8; /*udp header length is 8 bytes*/
	
	appLen = skb->len - dataOff;
	pAppData = pData + dataOff;

	//ftp command length is at least 14 bytes:port_1,1,1,1,1
	if (appLen < 14)
		return FAIL;

	//do nothing before napt modification
	if(after == 0)
		return SUCCESS;
	
	if(direct==NAPT_DIRECTION_OUTBOUND)
	{		
		if(pConn->tuple.isIp6==0)
		{
			pConn->tuple.extIp.ip = ntohl(*pPktHdr->pIpv4Sip);
			pConn->tuple.extPort = ntohs(*pPktHdr->pSport);
		}
	}

	pConn->skb=pSkb;
	pConn->pPktHdr=pPktInfo;

	ret = _rtk_rg_ftp_process(direct, pAppData, appLen, pConn);
	if(ret < 1)
		return FAIL;
#endif	
	return ret;
}


int _rtk_rg_server_in_wan_passive_mode_naptPriority_assign(unsigned char *pPktInfo, rtk_rg_alg_connection_t * pConn){
	//This API is used for parsing the remote server selected port, and assign naprPriorty to data flow.

#ifdef __KERNEL__
	int index;
	rtk_rg_pktHdr_t *pPktHdr;
	rtk_rg_naptFilterAndQos_t napt_filter;

	pPktHdr = (rtk_rg_pktHdr_t *)pPktInfo;

	if((pPktHdr->tagif&IPV4_TAGIF)&&((pPktHdr->tagif&TCP_TAGIF)||(pPktHdr->tagif&UDP_TAGIF))){//FTP only translated by TCP or UDP.
		if(pPktHdr->pL4Payload!=NULL && (*((unsigned int *)pPktHdr->pL4Payload) == htonl(ALG_FTP_PASV_RESP_STR))){ //The PASV request packet start with 227(ASCii) in payload.
			//check is outbound priority need to assigned 
			if(rg_db.naptOut[pPktHdr->naptOutboundIndx].priValid==ENABLED){
				bzero(&napt_filter,sizeof(napt_filter));
				napt_filter.direction=RTK_RG_NAPT_FILTER_OUTBOUND;
				napt_filter.filter_fields = (INGRESS_DIP|INGRESS_DPORT);
				napt_filter.ingress_dest_ipv4_addr = pConn->tuple.remoteIp.ip;
				napt_filter.ingress_dest_l4_port = pConn->tuple.remotePort;
				napt_filter.action_fields = ASSIGN_NAPT_PRIORITY_BIT;
				napt_filter.assign_priority = rg_db.naptOut[pPktHdr->naptOutboundIndx].priValue;//follow control flow.
				napt_filter.ruleType = RTK_RG_NAPT_FILTER_ONE_SHOT;
				assert_ok((pf.rtk_rg_naptFilterAndQos_add)(&index,&napt_filter));		
				TRACE("FTP ALG add outbound naptFilter[%d](DIP=0x%x + dport=%d => priority=%d, one shot) for data flow (The priority is based on napt[%d])",index,pConn->tuple.remoteIp,pConn->tuple.remotePort,rg_db.naptOut[pPktHdr->naptOutboundIndx].priValue,pPktHdr->naptOutboundIndx);		
			}

			//check is inbound priority need to assigned 
			if(rg_db.naptIn[pPktHdr->naptrInboundIndx].priValid==ENABLED){
				bzero(&napt_filter,sizeof(napt_filter));
				napt_filter.direction=RTK_RG_NAPT_FILTER_INBOUND;
				napt_filter.filter_fields = (INGRESS_SIP|INGRESS_SPORT);
				napt_filter.ingress_src_ipv4_addr = pConn->tuple.remoteIp.ip;
				napt_filter.ingress_src_l4_port = pConn->tuple.remotePort;
				napt_filter.action_fields = ASSIGN_NAPT_PRIORITY_BIT;
				napt_filter.assign_priority = rg_db.naptIn[pPktHdr->naptrInboundIndx].priValue;//follow control flow.
				napt_filter.ruleType = RTK_RG_NAPT_FILTER_ONE_SHOT;
				assert_ok((pf.rtk_rg_naptFilterAndQos_add)(&index,&napt_filter)); 	
				TRACE("FTP ALG add inbound naptFilter[%d](SIP=0x%x + sport=%d => priority=%d, one shot) for data flow (The priority is based on naptr[%d])",index,pConn->tuple.remoteIp,pConn->tuple.remotePort,rg_db.naptIn[pPktHdr->naptrInboundIndx].priValue,pPktHdr->naptrInboundIndx);		
			}
		}
	}
#endif	
	
	return SUCCESS;
}


rtk_rg_fwdEngineAlgReturn_t rtk_rg_algRegFunc_ftp(int direct, int after, unsigned char *pSkb,unsigned char *pPktInfo)
{
//Attention: caller function needs to make sure it needs to do napt modification
//ipv6 address and port doesn't need to do napt modification now
#ifdef __KERNEL__
	int ret;
	rtk_rg_pktHdr_t *pPktHdr;
	rtk_rg_alg_connection_t * pConn;
	rtk_rg_alg_tuple_t tuple;
	pPktHdr = (rtk_rg_pktHdr_t *)pPktInfo;
	memset(&tuple, 0, sizeof(rtk_rg_alg_tuple_t));
	
	if(after==0)
	{
		//Pre function
		if(direct==NAPT_DIRECTION_OUTBOUND)
		{
			_rtk_rg_alg_init_tuple(direct, after, pPktHdr, &tuple);
			
			pConn = _rtk_rg_alg_connection_find(&tuple);
			if(pConn == NULL)
			{

				pConn = _rtk_rg_alg_connection_add(&tuple);	
			}
			else
				_rtk_rg_sync_tcp_ack_seq(direct, pPktHdr, pConn);
		}
	}
	else
	{
		//Post function
		if(direct==NAPT_DIRECTION_OUTBOUND)
		{		
			_rtk_rg_alg_init_tuple(direct, after, pPktHdr, &tuple);
			
			pConn = _rtk_rg_alg_connection_find(&tuple);		
			if(pConn == NULL)
				return RG_FWDENGINE_ALG_RET_FAIL;
			
			ret = _rtk_rg_ftp_handler(direct, after, pSkb, pPktInfo, (unsigned char *)pConn);	
		}
		else
		{
			//Attention, pPktHdr->ipv4Dip is not the original external ip after napt modification, it is the internal ip
			_rtk_rg_alg_init_tuple(direct, after, pPktHdr, &tuple);
	
			pConn = _rtk_rg_alg_connection_find(&tuple);		
			if(pConn == NULL)
				return RG_FWDENGINE_ALG_RET_FAIL;

			_rtk_rg_sync_tcp_ack_seq(direct, pPktHdr, pConn);

			_rtk_rg_server_in_wan_passive_mode_naptPriority_assign(pPktInfo, pConn);
		}
	}
#endif
	return RG_FWDENGINE_ALG_RET_SUCCESS;
}

#ifdef __KERNEL__
static int __init init(void)
{
	return SUCCESS;
}


static void __exit exit(void)
{
}

module_init(init);
module_exit(exit);

#endif