#ifndef	__IP6_FASTPATH_CORE_H__
#define	__IP6_FASTPATH_CORE_H__

#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/in.h>
#include <linux/in6.h>
#include <linux/ipv6.h>
#include <net/ipv6.h>
#include <linux/udp.h>
#include <linux/tcp.h>
#include <linux/netdevice.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv6.h>
#include <net/dst.h>
#include <linux/init.h>
#include <net/route.h>

#include <net/netfilter/nf_conntrack_tuple.h>
#include <net/netfilter/nf_conntrack.h>
#include <net/netfilter/nf_conntrack_ecache.h>
#include <net/netfilter/nf_conntrack_core.h>
#include <net/netfilter/nf_conntrack_zones.h>

#include <linux/if_arp.h>

#include "../ipv4/fastpath_core.h"

#if 1
#define USE_SKB_DEV	1
#else
#undef USE_SKB_DEV
#endif

/* ---------------------------------------------------------------------------------------------------- */
#define	IP6_NAPT_TABLE_LIST_MAX		2048
#define	IP6_NAPT_TABLE_ENTRY_MAX	2048
#define	IP6_PATH_TABLE_LIST_MAX		2048
#define	IP6_PATH_TABLE_ENTRY_MAX	(IP6_NAPT_TABLE_ENTRY_MAX*2)

#define INET6_ADDRSTRLEN	46

//#define IP6_FP_DBG	1
#define IP6_FP_DBG	0


#if IP6_FP_DBG
#define IP6_DEBUGP_API printk
#else
#define IP6_DEBUGP_API(format, args...)
#endif

#if IP6_FP_DBG
#define IP6_DEBUGP_PKT printk
#else
#define IP6_DEBUGP_PKT(format, args...)
#endif

#if IP6_FP_DBG
#define IP6_DEBUGP_SYS printk
#else
#define IP6_DEBUGP_SYS(format, args...)
#endif

/* [MARCO FUNCTION] ========================================================================= */
#define IP6_FASTPATH_ADJUST_CHKSUM_NAT_UDP(ip_mod, ip_org, chksum) \
	do { \
		s32 accumulate = 0; \
		int i; \
		if (chksum == 0) break; \
		if (!ipv6_addr_any(ip_mod) && !ipv6_addr_any(ip_org)){ \
			for (i=0; i<8; i++) { \
				accumulate += (ip_org->s6_addr16[i]); \
			} \
			for (i=0; i<8; i++) { \
				accumulate -= (ip_mod->s6_addr16[i]); \
			} \
		} \
		accumulate += ntohs(chksum); \
		if (accumulate < 0) { \
			accumulate = -accumulate; \
			accumulate = (accumulate >> 16) + (accumulate & 0xffff); \
			accumulate += accumulate >> 16; \
			chksum = htons((__u16) ~accumulate); \
		} else { \
			accumulate = (accumulate >> 16) + (accumulate & 0xffff); \
			accumulate += accumulate >> 16; \
			chksum = htons((__u16) accumulate); \
		} \
	}while(0)	/* Checksum adjustment */

#define IP6_FASTPATH_ADJUST_CHKSUM_NPT_UDP(port_mod, port_org, chksum) \
	do { \
		s32 accumulate = 0; \
		if (chksum == 0) break; \
		if (((port_mod) != 0) && ((port_org) != 0)){ \
			accumulate += (port_org); \
			accumulate -= (port_mod); \
		} \
		accumulate += ntohs(chksum); \
		if (accumulate < 0) { \
			accumulate = -accumulate; \
			accumulate = (accumulate >> 16) + (accumulate & 0xffff); \
			accumulate += accumulate >> 16; \
			chksum = htons((__u16) ~accumulate); \
		} else { \
			accumulate = (accumulate >> 16) + (accumulate & 0xffff); \
			accumulate += accumulate >> 16; \
			chksum = htons((__u16) accumulate); \
		} \
	}while(0)	/* Checksum adjustment */


#define IP6_FASTPATH_ADJUST_CHKSUM_NAPT_UDP(ip_mod, ip_org, port_mod, port_org, chksum) \
	do { \
		s32 accumulate = 0; \
		int i; \
		if (chksum == 0) break; \
		if (!ipv6_addr_any(ip_mod) && !ipv6_addr_any(ip_org)){ \
			for (i=0; i<8; i++) { \
				accumulate += (ip_org->s6_addr16[i]); \
			} \
			for (i=0; i<8; i++) { \
				accumulate -= (ip_mod->s6_addr16[i]); \
			} \
		} \
		if (((port_mod) != 0) && ((port_org) != 0)){ \
			accumulate += (port_org); \
			accumulate -= (port_mod); \
		} \
		accumulate += ntohs(chksum); \
		if (accumulate < 0) { \
			accumulate = -accumulate; \
			accumulate = (accumulate >> 16) + (accumulate & 0xffff); \
			accumulate += accumulate >> 16; \
			chksum = htons((__u16) ~accumulate); \
		} else { \
			accumulate = (accumulate >> 16) + (accumulate & 0xffff); \
			accumulate += accumulate >> 16; \
			chksum = htons((__u16) accumulate); \
		} \
	}while(0)	/* Checksum adjustment */

#define IP6_FASTPATH_ADJUST_CHKSUM_NAT(ip_mod, ip_org, chksum) \
	do { \
		s32 accumulate = 0; \
		int i; \
		if (!ipv6_addr_any(ip_mod) && !ipv6_addr_any(ip_org)) { \
			for (i=0; i<8; i++) { \
			accumulate += (ip_org->s6_addr16[i]); \
			} \
			for (i=0; i<8; i++) { \
				accumulate -= (ip_mod->s6_addr16[i]); \
			} \
		} \
		accumulate += ntohs(chksum); \
		if (accumulate < 0) { \
			accumulate = -accumulate; \
			accumulate = (accumulate >> 16) + (accumulate & 0xffff); \
			accumulate += accumulate >> 16; \
			chksum = htons((__u16) ~accumulate); \
		} else { \
			accumulate = (accumulate >> 16) + (accumulate & 0xffff); \
			accumulate += accumulate >> 16; \
			chksum = htons((__u16) accumulate); \
		} \
	}while(0)	/* Checksum adjustment */

#define IP6_FASTPATH_ADJUST_CHKSUM_NPT(port_mod, port_org, chksum) \
	do { \
		s32 accumulate = 0; \
		if (((port_mod) != 0) && ((port_org) != 0)){ \
			accumulate += (port_org); \
			accumulate -= (port_mod); \
		} \
		accumulate += ntohs(chksum); \
		if (accumulate < 0) { \
			accumulate = -accumulate; \
			accumulate = (accumulate >> 16) + (accumulate & 0xffff); \
			accumulate += accumulate >> 16; \
			chksum = htons((__u16) ~accumulate); \
		} else { \
			accumulate = (accumulate >> 16) + (accumulate & 0xffff); \
			accumulate += accumulate >> 16; \
			chksum = htons((__u16) accumulate); \
		} \
	}while(0)	/* Checksum adjustment */


#define IP6_FASTPATH_ADJUST_CHKSUM_NAPT(ip_mod, ip_org, port_mod, port_org, chksum) \
	do { \
		s32 accumulate = 0; \
		int i; \
		if (!ipv6_addr_any(ip_mod) && !ipv6_addr_any(ip_org)){ \
			for (i=0; i<8; i++) { \
				accumulate += (ip_org->s6_addr16[i]); \
			} \
			for (i=0; i<8; i++) { \
				accumulate -= (ip_mod->s6_addr16[i]); \
			} \
		} \
		if (((port_mod) != 0) && ((port_org) != 0)){ \
			accumulate += (port_org); \
			accumulate -= (port_mod); \
		} \
		accumulate += ntohs(chksum); \
		if (accumulate < 0) { \
			accumulate = -accumulate; \
			accumulate = (accumulate >> 16) + (accumulate & 0xffff); \
			accumulate += accumulate >> 16; \
			chksum = htons((__u16) ~accumulate); \
		} else { \
			accumulate = (accumulate >> 16) + (accumulate & 0xffff); \
			accumulate += accumulate >> 16; \
			chksum = htons((__u16) accumulate); \
		} \
	}while(0)	/* Checksum adjustment */

/* ---------------------------------------------------------------------------------------------------- */
#ifdef CONFIG_NF_CONNTRACK_IPV6
#if defined(USE_SKB_DEV)
enum LR_RESULT fastpath_addRoutedIp6NaptConnection(struct sk_buff *skb, struct nf_conn *ct, struct nf_conntrack_tuple *orig_tuple,
		struct nf_conntrack_tuple *reply_tuple, int state);
enum LR_RESULT fastpath_updateIp6NaptConnection(struct sk_buff *skb, struct nf_conntrack_tuple *orig_tuple,
		struct nf_conntrack_tuple *reply_tuple, unsigned int mark);
#else
enum LR_RESULT fastpath_addRoutedIp6NaptConnection(struct nf_conn *ct, struct nf_conntrack_tuple *orig_tuple,
		struct nf_conntrack_tuple *reply_tuple, int state);
enum LR_RESULT fastpath_updateIp6NaptConnection(struct nf_conntrack_tuple *orig_tuple,
		struct nf_conntrack_tuple *reply_tuple, unsigned int mark);
#endif
enum LR_RESULT fastpath_delIp6NaptConnection (struct nf_conntrack_tuple *orig_tuple,
		struct nf_conntrack_tuple *reply_tuple);
#else
enum LR_RESULT fastpath_addRoutedIp6NaptConnectionWithoutNFV6(struct ipv6hdr *ip6hdr);
enum LR_RESULT fastpath_delIp6NaptConnectionWithoutNFV6(struct ipv6hdr *ip6hdr);
#endif

int Ip6_FastPath_Enter(struct sk_buff *skb);
int Ip6_clearFastPathEntry(void);

/* ---------------------------------------------------------------------------------------------------- */
struct IP6_FP_NAPT_entry {
	struct in6_addr intIp;
	struct in6_addr extIp;
	struct in6_addr remIp;
	__u32 intPort;
	__u32 extPort;
	__u32 remPort;
	__u16 protocol;
};

/* --- NAPT Table Structures --- */
struct Ip6_Napt_List_Entry
{
	__u8 valid;
	__u16 protocol;
	struct in6_addr intIp;
	__u32 intPort;
	struct in6_addr extIp;
	__u32 extPort;
	struct in6_addr remIp;
	__u32 remPort;
	
	__u16 state;	//0-unreplied   1-established.  2-path exist
	__u16 refcnt;
	__u32 course;	//if no conntrack exist, we should use course to establish conntrack.
	CTAILQ_ENTRY(Ip6_Napt_List_Entry) napt_link;
	CTAILQ_ENTRY(Ip6_Napt_List_Entry) tqe_link;
};

/* --- PATH Table Structures --- */
struct Ip6_Path_List_Entry
{
	__u8				valid;
	__u16				*protocol;
	struct in6_addr		*in_sIp;
	__u32				*in_sPort;
	struct in6_addr		*in_dIp;
	__u32				*in_dPort;
	struct in6_addr		*out_sIp;
	__u32				*out_sPort;
	struct in6_addr		*out_dIp;
	__u32				*out_dPort;
	__u8				*out_ifname;
	__u8				course;			/* 1:Out-Bonud 2:In-Bound */
	void				*dst;			//struct dst_entry *dst;
	__u8				type;			//pure routing, SNAT, SNPT, DNAT, DNPT
	
	/* FIXME: below two field are reserved now, they are used for IPQoS */
	__u32				mark;			//for IP QoS, TC
	
	__u32				last_refresh_time;			//lifetime for this entry
	__u32				fdb_ageing;		//this member only for upstream
	void				* conn;			//conntrack
	struct nf_conntrack_tuple 	orig_tuple;		// tuple in IP_CT_DIR_ORIGINAL in conntrack
	__u32 pps;	/* packets per second */
	CTAILQ_ENTRY(Ip6_Path_List_Entry) path_link;
	CTAILQ_ENTRY(Ip6_Path_List_Entry) tqe_link;
};

extern int ip6_fp_on;
extern unsigned int br_ageing_time;

static inline int Ip6_FastPath_Enabled(void) {
	return ip6_fp_on;
}

#ifdef CONFIG_NF_CONNTRACK_IPV6
enum LR_RESULT ip6_fastpath_addNaptConnection(void *ct, struct IP6_FP_NAPT_entry *napt, int state);
enum LR_RESULT ip6_fastpath_delNaptConnection(struct IP6_FP_NAPT_entry *napt);
enum LR_RESULT ip6_fastpath_updateNaptConnection(struct IP6_FP_NAPT_entry *napt, unsigned int mark);
#else
enum LR_RESULT ip6_fastpath_addNaptConnectionWithoutNFV6(struct IP6_FP_NAPT_entry *napt, int course);
enum LR_RESULT ip6_fastpath_delNaptConnectionWithoutNFV6(struct IP6_FP_NAPT_entry *napt, int course);
#endif

int Ip6_FastPath_Process(void *pskb, struct ipv6hdr *ip6hdr);

int fp_ip6route_input(void *pSt  /*struct skbuff * */, struct ipv6hdr *iph, struct in6_addr *fp_dip, unsigned int course);
int isNeighCreated(void *pDst, void* pSkb);
int needFragment(void* pSkb, void *pDst);
void ip6_fp_br_fdb_update(void *pSt);
void updateConxTimer(struct Ip6_Path_List_Entry *ptr);
int Ip6_packetParser(struct ipv6hdr *ip6hdr, __u8 *protoType, 
					__u16 *sport, __u16 *dport, __u8 **l4hdr);
int ip6_finish_output3(void *pskb, unsigned int course);
char *ip6_sprintf(char *ip6buf, const struct in6_addr *addr);

extern void __ip6_route_input(struct sk_buff *skb, 
							struct in6_addr *saddr, struct in6_addr *daddr, 
							struct net_device *in_dev);
int ip6_dst_lookup(struct sock *sk, struct dst_entry **dst, struct flowi6 *fl);


#endif	/* __IP6_FASTPATH_CORE_H__ */